Uploaded sanitized BC250/ROCm Repository.
This commit is contained in:
@@ -0,0 +1,114 @@
|
||||
import time
|
||||
import queue
|
||||
import numpy as np
|
||||
try:
|
||||
import sounddevice as sd
|
||||
except (ImportError, OSError):
|
||||
sd = None
|
||||
from ..schema.protocol import SpeakerRequest, SpeakerResponse
|
||||
|
||||
def handle_command(cmd: SpeakerRequest, state: dict):
|
||||
if cmd is None or cmd.msg_type == "EXIT":
|
||||
state["stop"] = True
|
||||
return
|
||||
if cmd.msg_type == "STOP":
|
||||
state["current_data"] = np.zeros((0, 1), dtype=np.float32)
|
||||
state["started"] = False
|
||||
return
|
||||
if cmd.msg_type == "PAUSE":
|
||||
state["paused"] = True
|
||||
return
|
||||
if cmd.msg_type == "CONTINUE":
|
||||
state["paused"] = False
|
||||
return
|
||||
if cmd.msg_type == "AUDIO":
|
||||
if cmd.audio is not None and len(cmd.audio) > 0:
|
||||
state["current_data"] = np.concatenate(
|
||||
[state["current_data"], cmd.audio.reshape(-1, 1).astype(np.float32)],
|
||||
axis=0
|
||||
)
|
||||
|
||||
def sync_playback_status(state: dict, result_queue):
|
||||
if result_queue is None: return
|
||||
if state.get("paused", False):
|
||||
target = "PAUSED"
|
||||
elif state.get("started", False):
|
||||
target = "PLAYING"
|
||||
else:
|
||||
target = "IDLE"
|
||||
if target == state["playback_state"]:
|
||||
return
|
||||
msg_map = {"PAUSED": "PAUSED", "PLAYING": "STARTED", "IDLE": "FINISHED"}
|
||||
result_queue.put(SpeakerResponse(msg_type=msg_map[target]))
|
||||
state["playback_state"] = target
|
||||
|
||||
def fill_audio(outdata, frames, state: dict):
|
||||
if state.get("paused", False):
|
||||
outdata.fill(0)
|
||||
return
|
||||
if not state["started"]:
|
||||
if len(state["current_data"]) >= state["threshold"]:
|
||||
state["started"] = True
|
||||
else:
|
||||
outdata.fill(0)
|
||||
return
|
||||
avail = len(state["current_data"])
|
||||
to_copy = min(avail, frames)
|
||||
if to_copy > 0:
|
||||
outdata[:to_copy] = state["current_data"][:to_copy]
|
||||
state["current_data"] = state["current_data"][to_copy:]
|
||||
if to_copy < frames:
|
||||
outdata[to_copy:].fill(0)
|
||||
state["started"] = False
|
||||
|
||||
def speaker_worker_proc(play_queue, result_queue=None, sample_rate=24000):
|
||||
state = {
|
||||
"current_data": np.zeros((0, 1), dtype=np.float32),
|
||||
"started": False,
|
||||
"threshold": 1200,
|
||||
"stop": False,
|
||||
"paused": False,
|
||||
"playback_state": "IDLE"
|
||||
}
|
||||
|
||||
def audio_callback(outdata, frames, time_info, status):
|
||||
while True:
|
||||
try:
|
||||
command = play_queue.get_nowait()
|
||||
handle_command(command, state)
|
||||
except queue.Empty:
|
||||
break
|
||||
fill_audio(outdata, frames, state)
|
||||
sync_playback_status(state, result_queue)
|
||||
|
||||
if sd is None:
|
||||
# Headless mode: no audio device available
|
||||
if result_queue:
|
||||
result_queue.put(SpeakerResponse(msg_type="READY"))
|
||||
while not state.get("stop"):
|
||||
try:
|
||||
command = play_queue.get(timeout=0.5)
|
||||
handle_command(command, state)
|
||||
except queue.Empty:
|
||||
pass
|
||||
return
|
||||
|
||||
try:
|
||||
with sd.OutputStream(samplerate=sample_rate, channels=1, callback=audio_callback, blocksize=2048):
|
||||
if result_queue:
|
||||
result_queue.put(SpeakerResponse(msg_type="READY"))
|
||||
while True:
|
||||
time.sleep(0.2)
|
||||
if state.get("stop"): break
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
except Exception as e:
|
||||
print(f"⚠️ [SpeakerWorker] No audio device, running headless: {e}")
|
||||
if result_queue:
|
||||
result_queue.put(SpeakerResponse(msg_type="READY"))
|
||||
while not state.get("stop"):
|
||||
try:
|
||||
command = play_queue.get(timeout=0.5)
|
||||
handle_command(command, state)
|
||||
except Exception:
|
||||
pass
|
||||
Reference in New Issue
Block a user