From 291c01fd98d44e42bb0a65468e3b6cee3dc25044 Mon Sep 17 00:00:00 2001
From: 魏曹先生 <1992414357@qq.com>
Date: Sat, 20 Jun 2026 20:45:31 +0800
Subject: feat(unity): add DMVOPListener for real-time voice input via TCP
---
binding/unity/DMVOPListener.cs | 146 +++++++++++++++++++++++++++++++++++++++++
1 file changed, 146 insertions(+)
create mode 100644 binding/unity/DMVOPListener.cs
(limited to 'binding/unity')
diff --git a/binding/unity/DMVOPListener.cs b/binding/unity/DMVOPListener.cs
new file mode 100644
index 0000000..7964756
--- /dev/null
+++ b/binding/unity/DMVOPListener.cs
@@ -0,0 +1,146 @@
+using System;
+using System.IO;
+using System.Net.Sockets;
+using System.Text;
+using System.Threading;
+using UnityEngine;
+using UnityEngine.Events;
+
+// Chinese:
+// dmvop --output=tcp --port=5117 --model=small --lang=zh --instant --pinyin --device=YOUR_DEVICE
+//
+// English
+// dmvop --output=tcp --port=5117 --model=small.en --lang=en --instant --device=YOUR_DEVICE
+
+[Serializable]
+public struct DMVOPEvent
+{
+ public float volume;
+ public string sentence;
+
+ public DMVOPEvent(float volume, string sentence)
+ {
+ this.volume = volume;
+ this.sentence = sentence;
+ }
+}
+
+///
+/// Connects to dmvop via TCP and receives voice input in real-time.
+/// Attach to any GameObject. Wire up OnVoiceInput in the Inspector.
+///
+public class DMVOPListener : MonoBehaviour
+{
+ [Header("DMVOP Connection")]
+ public string host = "127.0.0.1";
+ public int port = 5117;
+ public bool connectOnStart = true;
+
+ [Header("Events")]
+ public UnityEvent onVoiceInput;
+
+ private TcpClient _client;
+ private Thread _readerThread;
+ private volatile bool _running;
+ private readonly object _lockObj = new();
+ private DMVOPEvent _pendingEvent;
+ private bool _hasPending;
+
+ private void Start()
+ {
+ if (connectOnStart) Connect();
+ }
+
+ private void Connect()
+ {
+ if (_running) return;
+ _running = true;
+
+ try
+ {
+ _client = new TcpClient();
+ _client.Connect(host, port);
+ Debug.Log($"[DMVOP] Connected to {host}:{port}");
+
+ _readerThread = new Thread(ReadLoop);
+ _readerThread.IsBackground = true;
+ _readerThread.Start();
+ }
+ catch (Exception e)
+ {
+ Debug.LogError($"[DMVOP] Failed to connect: {e.Message}");
+ _running = false;
+ }
+ }
+
+ private void Disconnect()
+ {
+ _running = false;
+ if (_client != null)
+ {
+ _client.Close();
+ _client = null;
+ }
+ }
+
+ private void OnDestroy()
+ {
+ Disconnect();
+ }
+
+ private void Update()
+ {
+ if (!_hasPending) return;
+
+ DMVOPEvent evt;
+ lock (_lockObj)
+ {
+ evt = _pendingEvent;
+ _hasPending = false;
+ }
+
+ Debug.Log($"[DMVOP] Received event: volume={evt.volume}, sentence='{evt.sentence}'");
+ onVoiceInput?.Invoke(evt);
+ }
+
+ private void ReadLoop()
+ {
+ try
+ {
+ using var stream = _client.GetStream();
+ using var reader = new StreamReader(stream, Encoding.UTF8);
+
+ while (_running)
+ {
+ string line = reader.ReadLine();
+ if (line == null) break;
+
+ var evt = ParseLine(line);
+ lock (_lockObj)
+ {
+ _pendingEvent = evt;
+ _hasPending = true;
+ }
+ }
+ }
+ catch (Exception e) when (_running)
+ {
+ Debug.LogError($"[DMVOP] Connection lost: {e.Message}");
+ }
+ finally
+ {
+ _running = false;
+ Debug.Log("[DMVOP] Disconnected");
+ }
+ }
+
+ private static DMVOPEvent ParseLine(string line)
+ {
+ int comma = line.IndexOf(',');
+ if (comma > 0 && float.TryParse(line.AsSpan(0, comma), out float vol))
+ {
+ return new DMVOPEvent(vol, line.Substring(comma + 1));
+ }
+ return new DMVOPEvent(0, line);
+ }
+}
--
cgit