mirror of
https://github.com/huggingface/lerobot.git
synced 2026-05-30 18:31:25 +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 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 queue import Empty
|
||||
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 .configuration_portaudio import PortAudioMicrophoneConfig
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class PortAudioMicrophone(Microphone):
|
||||
"""
|
||||
@@ -152,7 +158,7 @@ class PortAudioMicrophone(Microphone):
|
||||
return found_microphones_info[0]
|
||||
|
||||
if len(found_microphones_info) == 0:
|
||||
logging.warning("No microphone found !")
|
||||
logger.warning("No microphone found !")
|
||||
|
||||
return found_microphones_info
|
||||
|
||||
@@ -206,7 +212,7 @@ class PortAudioMicrophone(Microphone):
|
||||
)
|
||||
else:
|
||||
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."
|
||||
)
|
||||
else:
|
||||
@@ -220,9 +226,7 @@ class PortAudioMicrophone(Microphone):
|
||||
]
|
||||
|
||||
if self.channels is not None and len(self.channels) > 0:
|
||||
if any(
|
||||
all(c > actual_channels) or c <= 0 or not isinstance(c, np.integer) for c in self.channels
|
||||
):
|
||||
if not all(channel in actual_channels for channel in self.channels):
|
||||
raise RuntimeError(
|
||||
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.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:
|
||||
raise RuntimeError(f"Error connecting microphone {self.microphone_index}.")
|
||||
|
||||
logger.info(f"{self} connected.")
|
||||
|
||||
def disconnect(self) -> None:
|
||||
"""
|
||||
Disconnects the microphone and stops the recording.
|
||||
@@ -295,6 +303,8 @@ class PortAudioMicrophone(Microphone):
|
||||
if self.is_connected:
|
||||
raise RuntimeError(f"Error disconnecting microphone {self.microphone_index}.")
|
||||
|
||||
logger.info(f"{self} disconnected.")
|
||||
|
||||
def _read(self) -> np.ndarray:
|
||||
"""
|
||||
Thread/Process-safe callback to read available audio data
|
||||
@@ -356,15 +366,13 @@ class PortAudioMicrophone(Microphone):
|
||||
Low-level sounddevice callback.
|
||||
"""
|
||||
if status:
|
||||
logging.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.
|
||||
logger.warning(status)
|
||||
if audio_callback_start_event.is_set():
|
||||
write_queue.put_nowait(indata[:, channels_index])
|
||||
read_queue.put_nowait(indata[:, channels_index])
|
||||
|
||||
# Create the audio stream
|
||||
# InputStream must be instantiated in the process as it is not pickable.
|
||||
stream = sounddevice_sdk.InputStream(
|
||||
device=microphone_index,
|
||||
samplerate=sample_rate,
|
||||
@@ -385,9 +393,8 @@ class PortAudioMicrophone(Microphone):
|
||||
stream.start()
|
||||
record_is_started_event.set()
|
||||
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()
|
||||
# Remark : stream.abort() flushes the buffers !
|
||||
stream.close()
|
||||
|
||||
def start_recording(
|
||||
@@ -488,6 +495,11 @@ class PortAudioMicrophone(Microphone):
|
||||
self.write_stop_event.set()
|
||||
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:
|
||||
raise RuntimeError(f"Error stopping recording for microphone {self.microphone_index}.")
|
||||
if self.is_writing:
|
||||
@@ -516,6 +528,10 @@ class PortAudioMicrophone(Microphone):
|
||||
except Empty:
|
||||
continue
|
||||
|
||||
def __del__(self) -> None:
|
||||
if self.is_connected:
|
||||
self.disconnect()
|
||||
|
||||
@staticmethod
|
||||
def _clear_queue(queue, join_queue: bool = False):
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user