feat(xiaozhi): 支持在 python 端监听 event 和 runShell
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
# 启动主循环线程
|
||||
|
||||
Reference in New Issue
Block a user