mirror of
https://github.com/huggingface/lerobot.git
synced 2026-07-24 10:16:09 +00:00
fix(Microphone): adding proper logger definition, timeout for end of recording monitoring and failsafe __del__ method
This commit is contained in:
@@ -18,7 +18,11 @@ Provides the PortAudioMicrophone class for capturing audio from microphones usin
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
import time
|
import time
|
||||||
from multiprocessing import Event as process_Event, JoinableQueue as process_Queue, Process
|
from multiprocessing import (
|
||||||
|
Event as process_Event,
|
||||||
|
JoinableQueue as process_Queue,
|
||||||
|
Process,
|
||||||
|
)
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from queue import Empty
|
from queue import Empty
|
||||||
from threading import Barrier, Event, Event as thread_Event, Thread
|
from threading import Barrier, Event, Event as thread_Event, Thread
|
||||||
@@ -38,6 +42,8 @@ from lerobot.microphones.portaudio.interface_sounddevice_sdk import ISounddevice
|
|||||||
from ..microphone import Microphone
|
from ..microphone import Microphone
|
||||||
from .configuration_portaudio import PortAudioMicrophoneConfig
|
from .configuration_portaudio import PortAudioMicrophoneConfig
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class PortAudioMicrophone(Microphone):
|
class PortAudioMicrophone(Microphone):
|
||||||
"""
|
"""
|
||||||
@@ -152,7 +158,7 @@ class PortAudioMicrophone(Microphone):
|
|||||||
return found_microphones_info[0]
|
return found_microphones_info[0]
|
||||||
|
|
||||||
if len(found_microphones_info) == 0:
|
if len(found_microphones_info) == 0:
|
||||||
logging.warning("No microphone found !")
|
logger.warning("No microphone found !")
|
||||||
|
|
||||||
return found_microphones_info
|
return found_microphones_info
|
||||||
|
|
||||||
@@ -206,7 +212,7 @@ class PortAudioMicrophone(Microphone):
|
|||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
if self.sample_rate < actual_sample_rate:
|
if self.sample_rate < actual_sample_rate:
|
||||||
logging.warning(
|
logger.warning(
|
||||||
"Provided sample rate is lower than the sample rate of the microphone. Performance may be impacted."
|
"Provided sample rate is lower than the sample rate of the microphone. Performance may be impacted."
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
@@ -220,9 +226,7 @@ class PortAudioMicrophone(Microphone):
|
|||||||
]
|
]
|
||||||
|
|
||||||
if self.channels is not None and len(self.channels) > 0:
|
if self.channels is not None and len(self.channels) > 0:
|
||||||
if any(
|
if not all(channel in actual_channels for channel in self.channels):
|
||||||
all(c > actual_channels) or c <= 0 or not isinstance(c, np.integer) for c in self.channels
|
|
||||||
):
|
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
f"Some of the provided channels {self.channels} are outside the possible channel range of the microphone {actual_channels}."
|
f"Some of the provided channels {self.channels} are outside the possible channel range of the microphone {actual_channels}."
|
||||||
)
|
)
|
||||||
@@ -273,10 +277,14 @@ class PortAudioMicrophone(Microphone):
|
|||||||
self.record_process.daemon = True
|
self.record_process.daemon = True
|
||||||
self.record_process.start()
|
self.record_process.start()
|
||||||
|
|
||||||
time.sleep(0.1) # Wait for the recording process to be started...
|
time.sleep(
|
||||||
|
0.1
|
||||||
|
) # Wait for the recording process to be started, and to potentially raise an error on failure.
|
||||||
if not self.is_connected:
|
if not self.is_connected:
|
||||||
raise RuntimeError(f"Error connecting microphone {self.microphone_index}.")
|
raise RuntimeError(f"Error connecting microphone {self.microphone_index}.")
|
||||||
|
|
||||||
|
logger.info(f"{self} connected.")
|
||||||
|
|
||||||
def disconnect(self) -> None:
|
def disconnect(self) -> None:
|
||||||
"""
|
"""
|
||||||
Disconnects the microphone and stops the recording.
|
Disconnects the microphone and stops the recording.
|
||||||
@@ -295,6 +303,8 @@ class PortAudioMicrophone(Microphone):
|
|||||||
if self.is_connected:
|
if self.is_connected:
|
||||||
raise RuntimeError(f"Error disconnecting microphone {self.microphone_index}.")
|
raise RuntimeError(f"Error disconnecting microphone {self.microphone_index}.")
|
||||||
|
|
||||||
|
logger.info(f"{self} disconnected.")
|
||||||
|
|
||||||
def _read(self) -> np.ndarray:
|
def _read(self) -> np.ndarray:
|
||||||
"""
|
"""
|
||||||
Thread/Process-safe callback to read available audio data
|
Thread/Process-safe callback to read available audio data
|
||||||
@@ -356,15 +366,13 @@ class PortAudioMicrophone(Microphone):
|
|||||||
Low-level sounddevice callback.
|
Low-level sounddevice callback.
|
||||||
"""
|
"""
|
||||||
if status:
|
if status:
|
||||||
logging.warning(status)
|
logger.warning(status)
|
||||||
# Slicing makes copy unnecessary
|
|
||||||
# Two separate queues are necessary because .get() also pops the data from the queue
|
|
||||||
# Remark: this also ensures that file-recorded data and chunk-audio data are the same.
|
|
||||||
if audio_callback_start_event.is_set():
|
if audio_callback_start_event.is_set():
|
||||||
write_queue.put_nowait(indata[:, channels_index])
|
write_queue.put_nowait(indata[:, channels_index])
|
||||||
read_queue.put_nowait(indata[:, channels_index])
|
read_queue.put_nowait(indata[:, channels_index])
|
||||||
|
|
||||||
# Create the audio stream
|
# Create the audio stream
|
||||||
|
# InputStream must be instantiated in the process as it is not pickable.
|
||||||
stream = sounddevice_sdk.InputStream(
|
stream = sounddevice_sdk.InputStream(
|
||||||
device=microphone_index,
|
device=microphone_index,
|
||||||
samplerate=sample_rate,
|
samplerate=sample_rate,
|
||||||
@@ -385,9 +393,8 @@ class PortAudioMicrophone(Microphone):
|
|||||||
stream.start()
|
stream.start()
|
||||||
record_is_started_event.set()
|
record_is_started_event.set()
|
||||||
record_stop_event.wait()
|
record_stop_event.wait()
|
||||||
stream.stop() # stream.stop() waits for all buffers to be processed
|
stream.stop() # stream.stop() waits for all buffers to be processed, stream.abort() flushes the buffers !
|
||||||
record_is_started_event.clear()
|
record_is_started_event.clear()
|
||||||
# Remark : stream.abort() flushes the buffers !
|
|
||||||
stream.close()
|
stream.close()
|
||||||
|
|
||||||
def start_recording(
|
def start_recording(
|
||||||
@@ -488,6 +495,11 @@ class PortAudioMicrophone(Microphone):
|
|||||||
self.write_stop_event.set()
|
self.write_stop_event.set()
|
||||||
self.write_thread.join()
|
self.write_thread.join()
|
||||||
|
|
||||||
|
timeout = 1.0
|
||||||
|
while self.is_recording and timeout > 0:
|
||||||
|
time.sleep(0.01)
|
||||||
|
timeout -= 0.01
|
||||||
|
|
||||||
if self.is_recording:
|
if self.is_recording:
|
||||||
raise RuntimeError(f"Error stopping recording for microphone {self.microphone_index}.")
|
raise RuntimeError(f"Error stopping recording for microphone {self.microphone_index}.")
|
||||||
if self.is_writing:
|
if self.is_writing:
|
||||||
@@ -516,6 +528,10 @@ class PortAudioMicrophone(Microphone):
|
|||||||
except Empty:
|
except Empty:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
def __del__(self) -> None:
|
||||||
|
if self.is_connected:
|
||||||
|
self.disconnect()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _clear_queue(queue, join_queue: bool = False):
|
def _clear_queue(queue, join_queue: bool = False):
|
||||||
"""
|
"""
|
||||||
|
|||||||
Reference in New Issue
Block a user