feat(xiaozhi): 支持在 python 端监听 event 和 runShell

This commit is contained in:
Del Wang
2025-05-11 20:47:02 +08:00
parent e687280ab1
commit 59c3280938
10 changed files with 198 additions and 21 deletions
@@ -1,14 +1,15 @@
import logging
import queue
import numpy as np
import pyaudio
import opuslib
from xiaozhi.services.protocols.typing import AudioConfig
import time
import sys
import time
import numpy as np
import opuslib
import pyaudio
from xiaozhi.services.audio.stream import MyAudio
from xiaozhi.xiaoai import XiaoAi
from xiaozhi.services.protocols.typing import AudioConfig
from xiaozhi.xiaoai import XiaoAI
logger = logging.getLogger("AudioCodec")
@@ -24,14 +25,14 @@ class AudioCodec:
self.opus_encoder = None
self.opus_decoder = None
self.audio_decode_queue = queue.Queue()
self._is_closing = False
self._is_closing = False
self._initialize_audio()
def _initialize_audio(self):
"""初始化音频设备和编解码器"""
try:
self.audio = MyAudio() if XiaoAi.mode == "xiaoai" else pyaudio.PyAudio()
self.audio = MyAudio() if XiaoAI.mode == "xiaoai" else pyaudio.PyAudio()
# 初始化音频输入流
self.input_stream = self.audio.open(
@@ -50,7 +51,7 @@ class AudioCodec:
output=True,
frames_per_buffer=AudioConfig.FRAME_SIZE,
)
# 初始化Opus编码器
self.opus_encoder = opuslib.Encoder(
fs=AudioConfig.SAMPLE_RATE,
@@ -193,7 +194,7 @@ class AudioCodec:
if self.output_stream.is_active():
self.output_stream.stop_stream()
self.output_stream.close()
except Exception as e:
except Exception:
# logger.warning(f"关闭旧输出流时出错: {e}")
pass
+21
View File
@@ -0,0 +1,21 @@
import json
def to_set(data):
if isinstance(data, list):
return list(set(data))
return data
def json_encode(obj, pretty=False):
try:
return json.dumps(obj, ensure_ascii=False, indent=4 if pretty else None)
except Exception as _:
return None
def json_decode(text):
try:
return json.loads(text)
except Exception:
return None
+61 -4
View File
@@ -1,20 +1,23 @@
import argparse
import asyncio
import threading
import numpy as np
import open_xiaoai_server
from xiaozhi.services.audio.stream import GlobalStream
from xiaozhi.utils.base import json_decode
class XiaoAi:
class XiaoAI:
mode = "xiaoai"
loop = asyncio.new_event_loop()
sync_loop: asyncio.AbstractEventLoop = None
async_loop: asyncio.AbstractEventLoop = None
@classmethod
def setup_mode(cls):
parser = argparse.ArgumentParser(
description="小爱音箱接入小智 AI 演示 | by: https://del.wang"
description="小爱音箱接入小智 AI | by: https://del.wang"
)
parser.add_argument(
"--mode",
@@ -38,10 +41,64 @@ class XiaoAi:
return await open_xiaoai_server.on_output_data(data)
future = on_output_data_async(data)
cls.loop.run_until_complete(future)
cls.sync_loop.run_until_complete(future)
@classmethod
async def run_shell(cls, script: str, timeout_millis: float = 10 * 1000):
return await open_xiaoai_server.run_shell(script, timeout_millis)
@classmethod
async def on_event(cls, event: str):
event_json = json_decode(event) or {}
event_data = event_json.get("data", {})
event_type = event_json.get("event")
if not event_json.get("event"):
print(f"❌ Event 解析失败: {event}")
return
if event_type == "kws":
if event_data == "Started":
print("🔥 自定义唤醒词已开启")
await cls.run_shell("/usr/sbin/tts_play.sh '自定义唤醒词已开启'")
else:
keyword = event_data.get("Keyword")
print(f"🔥 自定义唤醒词: {keyword}")
elif event_type == "instruction" and event_data.get("NewLine"):
line = json_decode(event_data.get("NewLine"))
if (
line
and line.get("header", {}).get("namespace") == "SpeechRecognizer"
and line.get("header", {}).get("name") == "RecognizeResult"
):
text = line.get("payload", {}).get("results")[0].get("text")
if not text:
print("🔥 唤醒小爱同学")
elif text and line.get("payload", {}).get("is_final"):
print(f"🔥 收到用户指令: {text}")
@classmethod
def __init_background_event_loop(cls):
def run_event_loop():
cls.sync_loop = asyncio.new_event_loop()
cls.async_loop = asyncio.new_event_loop()
asyncio.set_event_loop(cls.async_loop)
cls.async_loop.run_forever()
thread = threading.Thread(target=run_event_loop, daemon=True)
thread.start()
@classmethod
def __on_event(cls, event: str):
asyncio.run_coroutine_threadsafe(
cls.on_event(event),
cls.async_loop,
)
@classmethod
async def init_xiaoai(cls):
GlobalStream().on_output_data = cls.on_output_data
open_xiaoai_server.register_fn("on_input_data", cls.on_input_data)
open_xiaoai_server.register_fn("on_event", cls.__on_event)
cls.__init_background_event_loop()
await open_xiaoai_server.start_server()
+2 -2
View File
@@ -15,7 +15,7 @@ from xiaozhi.services.protocols.typing import (
)
from xiaozhi.services.protocols.websocket_protocol import WebsocketProtocol
from xiaozhi.utils.config_manager import ConfigManager
from xiaozhi.xiaoai import XiaoAi
from xiaozhi.xiaoai import XiaoAI
# 配置日志
logger = logging.getLogger("XiaoZhi")
@@ -91,7 +91,7 @@ class XiaoZhi:
time.sleep(0.1)
# 初始化应用程序(移除自动连接)
asyncio.run_coroutine_threadsafe(XiaoAi.init_xiaoai(), self.loop)
asyncio.run_coroutine_threadsafe(XiaoAI.init_xiaoai(), self.loop)
asyncio.run_coroutine_threadsafe(self._initialize_without_connect(), self.loop)
# 启动主循环线程