-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathaudio_io.py
More file actions
101 lines (80 loc) · 2.83 KB
/
Copy pathaudio_io.py
File metadata and controls
101 lines (80 loc) · 2.83 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
"""Audio I/O adapter for recording and playback."""
from __future__ import annotations
import queue
import numpy as np
try:
import sounddevice as sd
except ImportError:
sd = None
class AudioIOError(RuntimeError):
"""Raised when audio I/O cannot be performed."""
def is_available() -> bool:
"""Return True when sounddevice backend is available."""
return sd is not None
def record_audio(fs: int, duration_seconds: float) -> np.ndarray:
"""Record mono audio and return a 1-D float32 array."""
if sd is None:
raise AudioIOError("sounddevice is not installed")
frames = int(duration_seconds * fs)
recording = sd.rec(frames, samplerate=fs, channels=1, dtype=np.float32)
sd.wait()
return recording.flatten()
def stop_playback() -> None:
"""Stop active playback if any."""
if sd is None:
raise AudioIOError("sounddevice is not installed")
sd.stop()
def play_audio(samples: np.ndarray, fs: int, blocking: bool = False) -> None:
"""Play a mono audio buffer."""
if sd is None:
raise AudioIOError("sounddevice is not installed")
sd.play(samples, samplerate=fs, blocking=blocking)
class RealtimeAudioStream:
"""Microphone stream wrapper with chunk queue for real-time processing."""
def __init__(self, fs: int, blocksize: int = 256):
if sd is None:
raise AudioIOError("sounddevice is not installed")
self.fs = fs
self.blocksize = blocksize
self._queue: queue.Queue[np.ndarray] = queue.Queue()
self._stream = None
def _callback(self, indata, frames, time, status):
if status:
return
self._queue.put(indata[:, 0].copy())
def start(self) -> None:
"""Start microphone streaming."""
if self._stream is not None:
return
self._stream = sd.InputStream(
samplerate=self.fs,
channels=1,
dtype=np.float32,
blocksize=self.blocksize,
callback=self._callback,
)
self._stream.start()
def stop(self) -> None:
"""Stop microphone streaming and release resources."""
if self._stream is None:
return
self._stream.stop()
self._stream.close()
self._stream = None
self.clear()
def read_chunks(self, max_chunks: int = 64) -> list[np.ndarray]:
"""Read currently available chunks without blocking."""
chunks: list[np.ndarray] = []
for _ in range(max_chunks):
try:
chunks.append(self._queue.get_nowait())
except queue.Empty:
break
return chunks
def clear(self) -> None:
"""Clear pending queued audio chunks."""
while True:
try:
self._queue.get_nowait()
except queue.Empty:
break