reachy-mini 1.2.5rc1__py3-none-any.whl → 1.2.11__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- reachy_mini/apps/app.py +24 -21
- reachy_mini/apps/manager.py +17 -3
- reachy_mini/apps/sources/hf_auth.py +92 -0
- reachy_mini/apps/sources/hf_space.py +1 -1
- reachy_mini/apps/sources/local_common_venv.py +199 -24
- reachy_mini/apps/templates/main.py.j2 +4 -3
- reachy_mini/daemon/app/dashboard/static/js/apps.js +9 -1
- reachy_mini/daemon/app/dashboard/static/js/appstore.js +228 -0
- reachy_mini/daemon/app/dashboard/static/js/logs.js +148 -0
- reachy_mini/daemon/app/dashboard/templates/logs.html +37 -0
- reachy_mini/daemon/app/dashboard/templates/sections/appstore.html +92 -0
- reachy_mini/daemon/app/dashboard/templates/sections/cache.html +82 -0
- reachy_mini/daemon/app/dashboard/templates/sections/daemon.html +5 -0
- reachy_mini/daemon/app/dashboard/templates/settings.html +1 -0
- reachy_mini/daemon/app/main.py +172 -7
- reachy_mini/daemon/app/models.py +8 -0
- reachy_mini/daemon/app/routers/apps.py +56 -0
- reachy_mini/daemon/app/routers/cache.py +58 -0
- reachy_mini/daemon/app/routers/hf_auth.py +57 -0
- reachy_mini/daemon/app/routers/logs.py +124 -0
- reachy_mini/daemon/app/routers/state.py +25 -1
- reachy_mini/daemon/app/routers/wifi_config.py +75 -0
- reachy_mini/daemon/app/services/bluetooth/bluetooth_service.py +1 -1
- reachy_mini/daemon/app/services/bluetooth/commands/WIFI_RESET.sh +8 -0
- reachy_mini/daemon/app/services/wireless/launcher.sh +8 -2
- reachy_mini/daemon/app/services/wireless/reachy-mini-daemon.service +13 -0
- reachy_mini/daemon/backend/abstract.py +29 -9
- reachy_mini/daemon/backend/mockup_sim/__init__.py +12 -0
- reachy_mini/daemon/backend/mockup_sim/backend.py +176 -0
- reachy_mini/daemon/backend/mujoco/backend.py +0 -5
- reachy_mini/daemon/backend/robot/backend.py +78 -5
- reachy_mini/daemon/daemon.py +46 -7
- reachy_mini/daemon/utils.py +71 -15
- reachy_mini/io/zenoh_client.py +26 -0
- reachy_mini/io/zenoh_server.py +10 -6
- reachy_mini/kinematics/nn_kinematics.py +2 -2
- reachy_mini/kinematics/placo_kinematics.py +15 -15
- reachy_mini/media/__init__.py +55 -1
- reachy_mini/media/audio_base.py +185 -13
- reachy_mini/media/audio_control_utils.py +60 -5
- reachy_mini/media/audio_gstreamer.py +97 -16
- reachy_mini/media/audio_sounddevice.py +120 -19
- reachy_mini/media/audio_utils.py +110 -5
- reachy_mini/media/camera_base.py +182 -11
- reachy_mini/media/camera_constants.py +132 -4
- reachy_mini/media/camera_gstreamer.py +42 -2
- reachy_mini/media/camera_opencv.py +83 -5
- reachy_mini/media/camera_utils.py +95 -7
- reachy_mini/media/media_manager.py +139 -6
- reachy_mini/media/webrtc_client_gstreamer.py +142 -13
- reachy_mini/media/webrtc_daemon.py +72 -7
- reachy_mini/motion/recorded_move.py +76 -2
- reachy_mini/reachy_mini.py +196 -40
- reachy_mini/tools/reflash_motors.py +1 -1
- reachy_mini/tools/scan_motors.py +86 -0
- reachy_mini/tools/setup_motor.py +49 -31
- reachy_mini/utils/interpolation.py +1 -1
- reachy_mini/utils/wireless_version/startup_check.py +278 -21
- reachy_mini/utils/wireless_version/update.py +44 -1
- {reachy_mini-1.2.5rc1.dist-info → reachy_mini-1.2.11.dist-info}/METADATA +7 -6
- {reachy_mini-1.2.5rc1.dist-info → reachy_mini-1.2.11.dist-info}/RECORD +65 -53
- {reachy_mini-1.2.5rc1.dist-info → reachy_mini-1.2.11.dist-info}/WHEEL +0 -0
- {reachy_mini-1.2.5rc1.dist-info → reachy_mini-1.2.11.dist-info}/entry_points.txt +0 -0
- {reachy_mini-1.2.5rc1.dist-info → reachy_mini-1.2.11.dist-info}/licenses/LICENSE +0 -0
- {reachy_mini-1.2.5rc1.dist-info → reachy_mini-1.2.11.dist-info}/top_level.txt +0 -0
reachy_mini/media/audio_base.py
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
"""Base classes for audio implementations.
|
|
2
2
|
|
|
3
3
|
The audio implementations support various backends and provide a unified
|
|
4
|
-
interface for audio input/output.
|
|
4
|
+
interface for audio input/output. This module defines the abstract base class
|
|
5
|
+
that all audio implementations should inherit from, ensuring consistent
|
|
6
|
+
API across different audio backends.
|
|
7
|
+
|
|
8
|
+
Available backends include:
|
|
9
|
+
- SoundDevice: Cross-platform audio backend using sounddevice library
|
|
10
|
+
- GStreamer: GStreamer-based audio backend for advanced audio processing
|
|
11
|
+
- WebRTC: WebRTC-based audio for real-time communication
|
|
12
|
+
|
|
5
13
|
"""
|
|
6
14
|
|
|
7
15
|
import logging
|
|
@@ -15,13 +23,36 @@ from reachy_mini.media.audio_control_utils import ReSpeaker, init_respeaker_usb
|
|
|
15
23
|
|
|
16
24
|
|
|
17
25
|
class AudioBase(ABC):
|
|
18
|
-
"""Abstract class for opening and managing audio devices.
|
|
26
|
+
"""Abstract class for opening and managing audio devices.
|
|
27
|
+
|
|
28
|
+
This class defines the interface that all audio implementations must follow.
|
|
29
|
+
It provides common audio parameters and methods for managing audio devices,
|
|
30
|
+
including microphone input and speaker output functionality.
|
|
31
|
+
|
|
32
|
+
Attributes:
|
|
33
|
+
SAMPLE_RATE (int): Default sample rate for audio operations (16000 Hz).
|
|
34
|
+
CHANNELS (int): Default number of audio channels (2 for stereo).
|
|
35
|
+
logger (logging.Logger): Logger instance for audio-related messages.
|
|
36
|
+
_respeaker (Optional[ReSpeaker]): ReSpeaker microphone array device handler.
|
|
37
|
+
|
|
38
|
+
"""
|
|
19
39
|
|
|
20
40
|
SAMPLE_RATE = 16000 # respeaker samplerate
|
|
21
41
|
CHANNELS = 2 # respeaker channels
|
|
22
42
|
|
|
23
43
|
def __init__(self, log_level: str = "INFO") -> None:
|
|
24
|
-
"""Initialize the audio device.
|
|
44
|
+
"""Initialize the audio device.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
log_level (str): Logging level for audio operations.
|
|
48
|
+
Options: 'DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'.
|
|
49
|
+
Default: 'INFO'.
|
|
50
|
+
|
|
51
|
+
Note:
|
|
52
|
+
This constructor initializes the logging system and attempts to detect
|
|
53
|
+
and initialize the ReSpeaker microphone array if available.
|
|
54
|
+
|
|
55
|
+
"""
|
|
25
56
|
self.logger = logging.getLogger(__name__)
|
|
26
57
|
self.logger.setLevel(log_level)
|
|
27
58
|
self._respeaker: Optional[ReSpeaker] = init_respeaker_usb()
|
|
@@ -33,48 +64,179 @@ class AudioBase(ABC):
|
|
|
33
64
|
|
|
34
65
|
@abstractmethod
|
|
35
66
|
def start_recording(self) -> None:
|
|
36
|
-
"""Start recording audio.
|
|
67
|
+
"""Start recording audio.
|
|
68
|
+
|
|
69
|
+
This method should initialize the audio recording system and prepare
|
|
70
|
+
it to capture audio data. After calling this method, get_audio_sample()
|
|
71
|
+
should be able to retrieve recorded audio data.
|
|
72
|
+
|
|
73
|
+
Note:
|
|
74
|
+
Implementations should handle any necessary resource allocation and
|
|
75
|
+
error checking. If recording cannot be started, implementations should
|
|
76
|
+
log appropriate error messages.
|
|
77
|
+
|
|
78
|
+
Raises:
|
|
79
|
+
RuntimeError: If audio recording cannot be started due to hardware
|
|
80
|
+
or configuration issues.
|
|
81
|
+
|
|
82
|
+
"""
|
|
37
83
|
pass
|
|
38
84
|
|
|
39
85
|
@abstractmethod
|
|
40
86
|
def get_audio_sample(self) -> Optional[npt.NDArray[np.float32]]:
|
|
41
|
-
"""Read audio data from the device. Returns the data or None if error.
|
|
87
|
+
"""Read audio data from the device. Returns the data or None if error.
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
Optional[npt.NDArray[np.float32]]: A numpy array containing audio samples
|
|
91
|
+
in float32 format, or None if no data is available or an error occurred.
|
|
92
|
+
|
|
93
|
+
The array shape is typically (num_samples,) for mono or
|
|
94
|
+
(num_samples, num_channels) for multi-channel audio.
|
|
95
|
+
|
|
96
|
+
Note:
|
|
97
|
+
This method should be called after start_recording() has been called.
|
|
98
|
+
The sample rate and number of channels can be obtained via
|
|
99
|
+
get_input_audio_samplerate() and get_input_channels() respectively.
|
|
100
|
+
|
|
101
|
+
Example:
|
|
102
|
+
>>> audio.start_recording()
|
|
103
|
+
>>> samples = audio.get_audio_sample()
|
|
104
|
+
>>> if samples is not None:
|
|
105
|
+
... print(f"Got {len(samples)} audio samples")
|
|
106
|
+
|
|
107
|
+
"""
|
|
42
108
|
pass
|
|
43
109
|
|
|
44
110
|
def get_input_audio_samplerate(self) -> int:
|
|
45
|
-
"""Get the input samplerate of the audio device.
|
|
111
|
+
"""Get the input samplerate of the audio device.
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
int: The sample rate in Hz at which audio is being captured.
|
|
115
|
+
Default is 16000 Hz.
|
|
116
|
+
|
|
117
|
+
Note:
|
|
118
|
+
This value represents the number of audio samples captured per second
|
|
119
|
+
for each channel.
|
|
120
|
+
|
|
121
|
+
"""
|
|
46
122
|
return self.SAMPLE_RATE
|
|
47
123
|
|
|
48
124
|
def get_output_audio_samplerate(self) -> int:
|
|
49
|
-
"""Get the
|
|
125
|
+
"""Get the output samplerate of the audio device.
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
int: The sample rate in Hz at which audio is being played back.
|
|
129
|
+
Default is 16000 Hz.
|
|
130
|
+
|
|
131
|
+
Note:
|
|
132
|
+
This value represents the number of audio samples played per second
|
|
133
|
+
for each channel.
|
|
134
|
+
|
|
135
|
+
"""
|
|
50
136
|
return self.SAMPLE_RATE
|
|
51
137
|
|
|
52
138
|
def get_input_channels(self) -> int:
|
|
53
|
-
"""Get the number of input channels of the audio device.
|
|
139
|
+
"""Get the number of input channels of the audio device.
|
|
140
|
+
|
|
141
|
+
Returns:
|
|
142
|
+
int: The number of audio input channels (e.g., 1 for mono, 2 for stereo).
|
|
143
|
+
Default is 2 channels.
|
|
144
|
+
|
|
145
|
+
Note:
|
|
146
|
+
For the ReSpeaker microphone array, this typically returns 2 channels
|
|
147
|
+
representing the stereo microphone configuration.
|
|
148
|
+
|
|
149
|
+
"""
|
|
54
150
|
return self.CHANNELS
|
|
55
151
|
|
|
56
152
|
def get_output_channels(self) -> int:
|
|
57
|
-
"""Get the number of output channels of the audio device.
|
|
153
|
+
"""Get the number of output channels of the audio device.
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
int: The number of audio output channels (e.g., 1 for mono, 2 for stereo).
|
|
157
|
+
Default is 2 channels.
|
|
158
|
+
|
|
159
|
+
Note:
|
|
160
|
+
This determines how audio data should be formatted when passed to
|
|
161
|
+
push_audio_sample() method.
|
|
162
|
+
|
|
163
|
+
"""
|
|
58
164
|
return self.CHANNELS
|
|
59
165
|
|
|
60
166
|
@abstractmethod
|
|
61
167
|
def stop_recording(self) -> None:
|
|
62
|
-
"""Close the audio device and release resources.
|
|
168
|
+
"""Close the audio device and release resources.
|
|
169
|
+
|
|
170
|
+
This method should stop any ongoing audio recording and release
|
|
171
|
+
all associated resources. After calling this method, get_audio_sample()
|
|
172
|
+
should return None until start_recording() is called again.
|
|
173
|
+
|
|
174
|
+
Note:
|
|
175
|
+
Implementations should ensure proper cleanup to prevent resource leaks.
|
|
176
|
+
|
|
177
|
+
"""
|
|
63
178
|
pass
|
|
64
179
|
|
|
65
180
|
@abstractmethod
|
|
66
181
|
def start_playing(self) -> None:
|
|
67
|
-
"""Start playing audio.
|
|
182
|
+
"""Start playing audio.
|
|
183
|
+
|
|
184
|
+
This method should initialize the audio playback system and prepare
|
|
185
|
+
it to receive audio data via push_audio_sample().
|
|
186
|
+
|
|
187
|
+
Note:
|
|
188
|
+
Implementations should handle any necessary resource allocation and
|
|
189
|
+
error checking. If playback cannot be started, implementations should
|
|
190
|
+
log appropriate error messages.
|
|
191
|
+
|
|
192
|
+
Raises:
|
|
193
|
+
RuntimeError: If audio playback cannot be started due to hardware
|
|
194
|
+
or configuration issues.
|
|
195
|
+
|
|
196
|
+
"""
|
|
197
|
+
pass
|
|
198
|
+
|
|
199
|
+
@abstractmethod
|
|
200
|
+
def set_max_output_buffers(self, max_buffers: int) -> None:
|
|
201
|
+
"""Set the maximum number of output buffers to queue in the player.
|
|
202
|
+
|
|
203
|
+
Args:
|
|
204
|
+
max_buffers (int): Maximum number of buffers to queue.
|
|
205
|
+
|
|
206
|
+
"""
|
|
68
207
|
pass
|
|
69
208
|
|
|
70
209
|
@abstractmethod
|
|
71
210
|
def push_audio_sample(self, data: npt.NDArray[np.float32]) -> None:
|
|
72
|
-
"""Push audio data to the output device.
|
|
211
|
+
"""Push audio data to the output device.
|
|
212
|
+
|
|
213
|
+
Args:
|
|
214
|
+
data (npt.NDArray[np.float32]): Audio samples to be played.
|
|
215
|
+
The array should contain float32 values typically in the range [-1.0, 1.0].
|
|
216
|
+
|
|
217
|
+
For mono audio: shape should be (num_samples,)
|
|
218
|
+
For stereo audio: shape should be (num_samples, 2)
|
|
219
|
+
|
|
220
|
+
Note:
|
|
221
|
+
This method should be called after start_playing() has been called.
|
|
222
|
+
The audio data will be played at the sample rate returned by
|
|
223
|
+
get_output_audio_samplerate().
|
|
224
|
+
|
|
225
|
+
"""
|
|
73
226
|
pass
|
|
74
227
|
|
|
75
228
|
@abstractmethod
|
|
76
229
|
def stop_playing(self) -> None:
|
|
77
|
-
"""Stop playing audio and release resources.
|
|
230
|
+
"""Stop playing audio and release resources.
|
|
231
|
+
|
|
232
|
+
This method should stop any ongoing audio playback and release
|
|
233
|
+
all associated resources. After calling this method, push_audio_sample()
|
|
234
|
+
calls will have no effect until start_playing() is called again.
|
|
235
|
+
|
|
236
|
+
Note:
|
|
237
|
+
Implementations should ensure proper cleanup to prevent resource leaks.
|
|
238
|
+
|
|
239
|
+
"""
|
|
78
240
|
pass
|
|
79
241
|
|
|
80
242
|
@abstractmethod
|
|
@@ -83,6 +245,16 @@ class AudioBase(ABC):
|
|
|
83
245
|
|
|
84
246
|
Args:
|
|
85
247
|
sound_file (str): Path to the sound file to play.
|
|
248
|
+
Supported formats depend on the specific implementation.
|
|
249
|
+
|
|
250
|
+
Note:
|
|
251
|
+
This is a convenience method that handles the complete playback
|
|
252
|
+
of a sound file from start to finish. For more control over
|
|
253
|
+
audio playback, use start_playing(), push_audio_sample(),
|
|
254
|
+
and stop_playing() methods.
|
|
255
|
+
|
|
256
|
+
Example:
|
|
257
|
+
>>> audio.play_sound("/path/to/sound.wav")
|
|
86
258
|
|
|
87
259
|
"""
|
|
88
260
|
pass
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
"""Allows tuning of the XMOS XVF3800 chip.
|
|
1
|
+
"""Allows tuning of the XMOS XVF3800 chip integrated in the Reachy Mini Audio card.
|
|
2
2
|
|
|
3
3
|
Example usage:
|
|
4
4
|
|
|
5
5
|
# Read a parameter
|
|
6
|
-
python
|
|
6
|
+
python audio_control_utils.py AUDIO_MGR_OP_L
|
|
7
7
|
# Output:
|
|
8
8
|
# ReadCMD: cmdid: 143, resid: 35, response: array('B', [0, 8, 0])
|
|
9
9
|
# AUDIO_MGR_OP_L: [0, 8, 0]
|
|
10
10
|
|
|
11
11
|
# Write a parameter
|
|
12
|
-
python
|
|
12
|
+
python audio_control_utils.py AUDIO_MGR_OP_L --values 3 0
|
|
13
13
|
# Output:
|
|
14
14
|
# Writing to AUDIO_MGR_OP_L with values: [3, 0]
|
|
15
15
|
# WriteCMD: cmdid: 15, resid: 35, payload: [3, 0]
|
|
@@ -320,7 +320,34 @@ class ReSpeaker:
|
|
|
320
320
|
|
|
321
321
|
|
|
322
322
|
def find(vid: int = 0x2886, pid: int = 0x001A) -> ReSpeaker | None:
|
|
323
|
-
"""Find and return the ReSpeaker USB device with the given Vendor ID and Product ID.
|
|
323
|
+
"""Find and return the ReSpeaker USB device with the given Vendor ID and Product ID.
|
|
324
|
+
|
|
325
|
+
Args:
|
|
326
|
+
vid (int): USB Vendor ID to search for. Default: 0x2886 (XMOS).
|
|
327
|
+
pid (int): USB Product ID to search for. Default: 0x001A (XMOS XVF3800).
|
|
328
|
+
|
|
329
|
+
Returns:
|
|
330
|
+
ReSpeaker | None: A ReSpeaker object if the device is found,
|
|
331
|
+
None otherwise.
|
|
332
|
+
|
|
333
|
+
Note:
|
|
334
|
+
This function searches for USB devices with the specified Vendor ID
|
|
335
|
+
and Product ID using libusb backend. The default values target
|
|
336
|
+
XMOS XVF3800 devices used in ReSpeaker microphone arrays.
|
|
337
|
+
|
|
338
|
+
Example:
|
|
339
|
+
>>> from reachy_mini.media.audio_control_utils import find
|
|
340
|
+
>>>
|
|
341
|
+
>>> # Find default ReSpeaker device
|
|
342
|
+
>>> respeaker = find()
|
|
343
|
+
>>> if respeaker is not None:
|
|
344
|
+
... print("Found ReSpeaker device")
|
|
345
|
+
... respeaker.close()
|
|
346
|
+
>>>
|
|
347
|
+
>>> # Find specific device
|
|
348
|
+
>>> custom_device = find(vid=0x1234, pid=0x5678)
|
|
349
|
+
|
|
350
|
+
"""
|
|
324
351
|
dev = usb.core.find(idVendor=vid, idProduct=pid, backend=get_libusb1_backend())
|
|
325
352
|
if not dev:
|
|
326
353
|
return None
|
|
@@ -329,7 +356,35 @@ def find(vid: int = 0x2886, pid: int = 0x001A) -> ReSpeaker | None:
|
|
|
329
356
|
|
|
330
357
|
|
|
331
358
|
def init_respeaker_usb() -> Optional[ReSpeaker]:
|
|
332
|
-
"""Initialize the ReSpeaker USB device. Looks for both new and beta device IDs.
|
|
359
|
+
"""Initialize the ReSpeaker USB device. Looks for both new and beta device IDs.
|
|
360
|
+
|
|
361
|
+
Returns:
|
|
362
|
+
Optional[ReSpeaker]: A ReSpeaker object if a compatible device is found,
|
|
363
|
+
None otherwise.
|
|
364
|
+
|
|
365
|
+
Note:
|
|
366
|
+
This function attempts to initialize a ReSpeaker microphone array by
|
|
367
|
+
searching for USB devices with known Vendor and Product IDs. It tries:
|
|
368
|
+
1. New Reachy Mini Audio firmware (0x38FB:0x1001) - preferred
|
|
369
|
+
2. Old ReSpeaker firmware (0x2886:0x001A) - with warning to update
|
|
370
|
+
|
|
371
|
+
The function handles USB backend errors gracefully and returns
|
|
372
|
+
None if no compatible device is found or if initialization fails.
|
|
373
|
+
|
|
374
|
+
Example:
|
|
375
|
+
>>> from reachy_mini.media.audio_control_utils import init_respeaker_usb
|
|
376
|
+
>>>
|
|
377
|
+
>>> # Initialize ReSpeaker device
|
|
378
|
+
>>> respeaker = init_respeaker_usb()
|
|
379
|
+
>>> if respeaker is not None:
|
|
380
|
+
... print("ReSpeaker initialized successfully")
|
|
381
|
+
... # Use the device...
|
|
382
|
+
... doa = respeaker.read("DOA_VALUE_RADIANS")
|
|
383
|
+
... respeaker.close()
|
|
384
|
+
... else:
|
|
385
|
+
... print("No ReSpeaker device found")
|
|
386
|
+
|
|
387
|
+
"""
|
|
333
388
|
try:
|
|
334
389
|
# Try new firmware first
|
|
335
390
|
dev = usb.core.find(
|
|
@@ -1,7 +1,45 @@
|
|
|
1
|
-
"""GStreamer
|
|
1
|
+
"""GStreamer audio backend.
|
|
2
|
+
|
|
3
|
+
This module provides an implementation of the AudioBase class using GStreamer.
|
|
4
|
+
It offers advanced audio processing capabilities including microphone input,
|
|
5
|
+
speaker output, and integration with the ReSpeaker microphone array for
|
|
6
|
+
Direction of Arrival (DoA) estimation.
|
|
7
|
+
|
|
8
|
+
The GStreamer audio backend supports:
|
|
9
|
+
- High-quality audio capture and playback
|
|
10
|
+
- ReSpeaker microphone array integration
|
|
11
|
+
- Direction of Arrival (DoA) estimation
|
|
12
|
+
- Advanced audio processing pipelines
|
|
13
|
+
- Multiple audio formats and sample rates
|
|
14
|
+
|
|
15
|
+
Note:
|
|
16
|
+
This class is typically used internally by the MediaManager when the GSTREAMER
|
|
17
|
+
backend is selected. Direct usage is possible but usually not necessary.
|
|
18
|
+
|
|
19
|
+
Example usage via MediaManager:
|
|
20
|
+
>>> from reachy_mini.media.media_manager import MediaManager, MediaBackend
|
|
21
|
+
>>>
|
|
22
|
+
>>> # Create media manager with GStreamer backend
|
|
23
|
+
>>> media = MediaManager(backend=MediaBackend.GSTREAMER, log_level="INFO")
|
|
24
|
+
>>>
|
|
25
|
+
>>> # Start audio recording
|
|
26
|
+
>>> media.start_recording()
|
|
27
|
+
>>>
|
|
28
|
+
>>> # Get audio samples
|
|
29
|
+
>>> samples = media.get_audio_sample()
|
|
30
|
+
>>> if samples is not None:
|
|
31
|
+
... print(f"Captured {len(samples)} audio samples")
|
|
32
|
+
>>>
|
|
33
|
+
>>> # Get Direction of Arrival
|
|
34
|
+
>>> doa = media.get_DoA()
|
|
35
|
+
>>> if doa is not None:
|
|
36
|
+
... angle, speech_detected = doa
|
|
37
|
+
... print(f"Sound direction: {angle} radians, speech detected: {speech_detected}")
|
|
38
|
+
>>>
|
|
39
|
+
>>> # Clean up
|
|
40
|
+
>>> media.stop_recording()
|
|
41
|
+
>>> media.close()
|
|
2
42
|
|
|
3
|
-
This module provides an implementation of the CameraBase class using GStreamer.
|
|
4
|
-
By default the module directly returns JPEG images as output by the camera.
|
|
5
43
|
"""
|
|
6
44
|
|
|
7
45
|
import os
|
|
@@ -108,6 +146,21 @@ class GStreamerAudio(AudioBase):
|
|
|
108
146
|
self._bus_record.remove_watch()
|
|
109
147
|
self._bus_playback.remove_watch()
|
|
110
148
|
|
|
149
|
+
def set_max_output_buffers(self, max_buffers: int) -> None:
|
|
150
|
+
"""Set the maximum number of output buffers to queue in the player.
|
|
151
|
+
|
|
152
|
+
Args:
|
|
153
|
+
max_buffers (int): Maximum number of buffers to queue.
|
|
154
|
+
|
|
155
|
+
"""
|
|
156
|
+
if self._appsrc is not None:
|
|
157
|
+
self._appsrc.set_property("max-buffers", max_buffers)
|
|
158
|
+
self._appsrc.set_property("leaky-type", 2) # drop old buffers
|
|
159
|
+
else:
|
|
160
|
+
self.logger.warning(
|
|
161
|
+
"AppSrc is not initialized. Call start_playing() first."
|
|
162
|
+
)
|
|
163
|
+
|
|
111
164
|
def _init_pipeline_playback(self, pipeline: Gst.Pipeline) -> None:
|
|
112
165
|
self._appsrc = Gst.ElementFactory.make("appsrc")
|
|
113
166
|
self._appsrc.set_property("format", Gst.Format.TIME)
|
|
@@ -120,7 +173,6 @@ class GStreamerAudio(AudioBase):
|
|
|
120
173
|
audioconvert = Gst.ElementFactory.make("audioconvert")
|
|
121
174
|
audioresample = Gst.ElementFactory.make("audioresample")
|
|
122
175
|
|
|
123
|
-
queue = Gst.ElementFactory.make("queue")
|
|
124
176
|
audiosink: Optional[Gst.Element] = None
|
|
125
177
|
if self._id_audio_card == -1:
|
|
126
178
|
audiosink = Gst.ElementFactory.make("autoaudiosink") # use default speaker
|
|
@@ -132,14 +184,12 @@ class GStreamerAudio(AudioBase):
|
|
|
132
184
|
audiosink = Gst.ElementFactory.make("alsasink")
|
|
133
185
|
audiosink.set_property("device", f"hw:{self._id_audio_card},0")
|
|
134
186
|
|
|
135
|
-
pipeline.add(queue)
|
|
136
187
|
pipeline.add(audiosink)
|
|
137
188
|
pipeline.add(self._appsrc)
|
|
138
189
|
pipeline.add(audioconvert)
|
|
139
190
|
pipeline.add(audioresample)
|
|
140
191
|
|
|
141
|
-
self._appsrc.link(
|
|
142
|
-
queue.link(audioconvert)
|
|
192
|
+
self._appsrc.link(audioconvert)
|
|
143
193
|
audioconvert.link(audioresample)
|
|
144
194
|
audioresample.link(audiosink)
|
|
145
195
|
|
|
@@ -157,7 +207,10 @@ class GStreamerAudio(AudioBase):
|
|
|
157
207
|
return True
|
|
158
208
|
|
|
159
209
|
def start_recording(self) -> None:
|
|
160
|
-
"""Open the audio card using GStreamer.
|
|
210
|
+
"""Open the audio card using GStreamer.
|
|
211
|
+
|
|
212
|
+
See AudioBase.start_recording() for complete documentation.
|
|
213
|
+
"""
|
|
161
214
|
self._pipeline_record.set_state(Gst.State.PLAYING)
|
|
162
215
|
|
|
163
216
|
def _get_sample(self, appsink: GstApp.AppSink) -> Optional[bytes]:
|
|
@@ -176,6 +229,8 @@ class GStreamerAudio(AudioBase):
|
|
|
176
229
|
def get_audio_sample(self) -> Optional[npt.NDArray[np.float32]]:
|
|
177
230
|
"""Read a sample from the audio card. Returns the sample or None if error.
|
|
178
231
|
|
|
232
|
+
See AudioBase.get_audio_sample() for complete documentation.
|
|
233
|
+
|
|
179
234
|
Returns:
|
|
180
235
|
Optional[npt.NDArray[np.float32]]: The captured sample in raw format, or None if error.
|
|
181
236
|
|
|
@@ -186,35 +241,59 @@ class GStreamerAudio(AudioBase):
|
|
|
186
241
|
return np.frombuffer(sample, dtype=np.float32).reshape(-1, 2)
|
|
187
242
|
|
|
188
243
|
def get_input_audio_samplerate(self) -> int:
|
|
189
|
-
"""Get the input samplerate of the audio device.
|
|
244
|
+
"""Get the input samplerate of the audio device.
|
|
245
|
+
|
|
246
|
+
See AudioBase.get_input_audio_samplerate() for complete documentation.
|
|
247
|
+
"""
|
|
190
248
|
return self.SAMPLE_RATE
|
|
191
249
|
|
|
192
250
|
def get_output_audio_samplerate(self) -> int:
|
|
193
|
-
"""Get the output samplerate of the audio device.
|
|
251
|
+
"""Get the output samplerate of the audio device.
|
|
252
|
+
|
|
253
|
+
See AudioBase.get_output_audio_samplerate() for complete documentation.
|
|
254
|
+
"""
|
|
194
255
|
return self.SAMPLE_RATE
|
|
195
256
|
|
|
196
257
|
def get_input_channels(self) -> int:
|
|
197
|
-
"""Get the number of input channels of the audio device.
|
|
258
|
+
"""Get the number of input channels of the audio device.
|
|
259
|
+
|
|
260
|
+
See AudioBase.get_input_channels() for complete documentation.
|
|
261
|
+
"""
|
|
198
262
|
return self.CHANNELS
|
|
199
263
|
|
|
200
264
|
def get_output_channels(self) -> int:
|
|
201
|
-
"""Get the number of output channels of the audio device.
|
|
265
|
+
"""Get the number of output channels of the audio device.
|
|
266
|
+
|
|
267
|
+
See AudioBase.get_output_channels() for complete documentation.
|
|
268
|
+
"""
|
|
202
269
|
return self.CHANNELS
|
|
203
270
|
|
|
204
271
|
def stop_recording(self) -> None:
|
|
205
|
-
"""Release the camera resource.
|
|
272
|
+
"""Release the camera resource.
|
|
273
|
+
|
|
274
|
+
See AudioBase.stop_recording() for complete documentation.
|
|
275
|
+
"""
|
|
206
276
|
self._pipeline_record.set_state(Gst.State.NULL)
|
|
207
277
|
|
|
208
278
|
def start_playing(self) -> None:
|
|
209
|
-
"""Open the audio output using GStreamer.
|
|
279
|
+
"""Open the audio output using GStreamer.
|
|
280
|
+
|
|
281
|
+
See AudioBase.start_playing() for complete documentation.
|
|
282
|
+
"""
|
|
210
283
|
self._pipeline_playback.set_state(Gst.State.PLAYING)
|
|
211
284
|
|
|
212
285
|
def stop_playing(self) -> None:
|
|
213
|
-
"""Stop playing audio and release resources.
|
|
286
|
+
"""Stop playing audio and release resources.
|
|
287
|
+
|
|
288
|
+
See AudioBase.stop_playing() for complete documentation.
|
|
289
|
+
"""
|
|
214
290
|
self._pipeline_playback.set_state(Gst.State.NULL)
|
|
215
291
|
|
|
216
292
|
def push_audio_sample(self, data: npt.NDArray[np.float32]) -> None:
|
|
217
|
-
"""Push audio data to the output device.
|
|
293
|
+
"""Push audio data to the output device.
|
|
294
|
+
|
|
295
|
+
See AudioBase.push_audio_sample() for complete documentation.
|
|
296
|
+
"""
|
|
218
297
|
if self._appsrc is not None:
|
|
219
298
|
buf = Gst.Buffer.new_wrapped(data.tobytes())
|
|
220
299
|
self._appsrc.push_buffer(buf)
|
|
@@ -226,6 +305,8 @@ class GStreamerAudio(AudioBase):
|
|
|
226
305
|
def play_sound(self, sound_file: str) -> None:
|
|
227
306
|
"""Play a sound file.
|
|
228
307
|
|
|
308
|
+
See AudioBase.play_sound() for complete documentation.
|
|
309
|
+
|
|
229
310
|
Todo: for now this function is mean to be used on the wireless version.
|
|
230
311
|
|
|
231
312
|
Args:
|