Renamming sampling rate to sample rate for consistency

This commit is contained in:
CarolinePascal
2025-04-08 16:24:44 +02:00
parent be09a59e05
commit 836195e59c
8 changed files with 27 additions and 39 deletions
+5 -7
View File
@@ -73,14 +73,14 @@ def decode_audio_torchaudio(
audio_path = str(audio_path) audio_path = str(audio_path)
reader = torchaudio.io.StreamReader(src=audio_path) reader = torchaudio.io.StreamReader(src=audio_path)
audio_sampling_rate = reader.get_src_stream_info(reader.default_audio_stream).sample_rate audio_sample_rate = reader.get_src_stream_info(reader.default_audio_stream).sample_rate
# TODO(CarolinePascal) : sort timestamps ? # TODO(CarolinePascal) : sort timestamps ?
reader.add_basic_audio_stream( reader.add_basic_audio_stream(
frames_per_chunk=int(ceil(duration * audio_sampling_rate)), # Too much is better than not enough frames_per_chunk = int(ceil(duration * audio_sample_rate)), #Too much is better than not enough
buffer_chunk_size=-1, # No dropping frames buffer_chunk_size = -1, #No dropping frames
format="fltp", # Format as float32 format = "fltp", #Format as float32
) )
audio_chunks = [] audio_chunks = []
@@ -93,9 +93,7 @@ def decode_audio_torchaudio(
current_audio_chunk = reader.pop_chunks()[0] current_audio_chunk = reader.pop_chunks()[0]
if log_loaded_timestamps: if log_loaded_timestamps:
logging.info( logging.info(f"audio chunk loaded at starting timestamp={current_audio_chunk['pts']:.4f} with duration={len(current_audio_chunk) / audio_sample_rate:.4f}")
f"audio chunk loaded at starting timestamp={current_audio_chunk['pts']:.4f} with duration={len(current_audio_chunk) / audio_sampling_rate:.4f}"
)
audio_chunks.append(current_audio_chunk) audio_chunks.append(current_audio_chunk)
+1 -1
View File
@@ -21,7 +21,7 @@ import draccus
@dataclass(kw_only=True) @dataclass(kw_only=True)
class MicrophoneConfig(draccus.ChoiceRegistry, abc.ABC): class MicrophoneConfig(draccus.ChoiceRegistry, abc.ABC):
microphone_index: int microphone_index: int
sampling_rate: int | None = None sample_rate: int | None = None
channels: list[int] | None = None channels: list[int] | None = None
@property @property
+16 -23
View File
@@ -73,7 +73,7 @@ def record_audio_from_microphones(
microphone = Microphone(config) microphone = Microphone(config)
microphone.connect() microphone.connect()
print( print(
f"Recording audio from microphone {microphone_id} for {record_time_s} seconds at {microphone.sampling_rate} Hz." f"Recording audio from microphone {microphone_id} for {record_time_s} seconds at {microphone.sample_rate} Hz."
) )
microphones.append(microphone) microphones.append(microphone)
@@ -105,13 +105,13 @@ class Microphone:
""" """
The Microphone class handles all microphones compatible with sounddevice (and the underlying PortAudio library). Most microphones and sound cards are compatible, across all OS (Linux, Mac, Windows). The Microphone class handles all microphones compatible with sounddevice (and the underlying PortAudio library). Most microphones and sound cards are compatible, across all OS (Linux, Mac, Windows).
A Microphone instance requires the sounddevice index of the microphone, which may be obtained using `python -m sounddevice`. It also requires the recording sampling rate as well as the list of recorded channels. A Microphone instance requires the sounddevice index of the microphone, which may be obtained using `python -m sounddevice`. It also requires the recording sample rate as well as the list of recorded channels.
Example of usage: Example of usage:
```python ```python
from lerobot.common.robot_devices.microphones.configs import MicrophoneConfig from lerobot.common.robot_devices.microphones.configs import MicrophoneConfig
config = MicrophoneConfig(microphone_index=0, sampling_rate=16000, channels=[1]) config = MicrophoneConfig(microphone_index=0, sample_rate=16000, channels=[1])
microphone = Microphone(config) microphone = Microphone(config)
microphone.connect() microphone.connect()
@@ -130,8 +130,8 @@ class Microphone:
self.config = config self.config = config
self.microphone_index = config.microphone_index self.microphone_index = config.microphone_index
# Store the recording sampling rate and channels #Store the recording sample rate and channels
self.sampling_rate = config.sampling_rate self.sample_rate = config.sample_rate
self.channels = config.channels self.channels = config.channels
# Input audio stream # Input audio stream
@@ -166,17 +166,15 @@ class Microphone:
# Check if provided recording parameters are compatible with the microphone # Check if provided recording parameters are compatible with the microphone
actual_microphone = sd.query_devices(self.microphone_index) actual_microphone = sd.query_devices(self.microphone_index)
if self.sampling_rate is not None: if self.sample_rate is not None :
if self.sampling_rate > actual_microphone["default_samplerate"]: if self.sample_rate > actual_microphone["default_samplerate"]:
raise OSError( raise OSError(
f"Provided sampling rate {self.sampling_rate} is higher than the sampling rate of the microphone {actual_microphone['default_samplerate']}." f"Provided sample rate {self.sample_rate} is higher than the sample rate of the microphone {actual_microphone['default_samplerate']}."
)
elif self.sampling_rate < actual_microphone["default_samplerate"]:
logging.warning(
"Provided sampling rate is lower than the sampling rate of the microphone. Performance may be impacted."
) )
elif self.sample_rate < actual_microphone["default_samplerate"]:
logging.warning("Provided sample rate is lower than the sample rate of the microphone. Performance may be impacted.")
else: else:
self.sampling_rate = int(actual_microphone["default_samplerate"]) self.sample_rate = int(actual_microphone["default_samplerate"])
if self.channels is not None: if self.channels is not None:
if any(c > actual_microphone["max_input_channels"] for c in self.channels): if any(c > actual_microphone["max_input_channels"] for c in self.channels):
@@ -192,8 +190,8 @@ class Microphone:
# Create the audio stream # Create the audio stream
self.stream = sd.InputStream( self.stream = sd.InputStream(
device=self.microphone_index, device=self.microphone_index,
samplerate=self.sampling_rate, samplerate=self.sample_rate,
channels=max(self.channels) + 1, channels=max(self.channels)+1,
dtype="float32", dtype="float32",
callback=self._audio_callback, callback=self._audio_callback,
) )
@@ -211,14 +209,9 @@ class Microphone:
self.read_queue.put(indata[:, self.channels]) self.read_queue.put(indata[:, self.channels])
def _record_loop(self, output_file: Path) -> None: def _record_loop(self, output_file: Path) -> None:
# Can only be run on a single process/thread for file writing safety #Can only be run on a single process/thread for file writing safety
with sf.SoundFile( with sf.SoundFile(output_file, mode='x', samplerate=self.sample_rate,
output_file, channels=max(self.channels)+1, subtype=sf.default_subtype(output_file.suffix[1:])) as file:
mode="x",
samplerate=self.sampling_rate,
channels=max(self.channels) + 1,
subtype=sf.default_subtype(output_file.suffix[1:]),
) as file:
while not self.record_stop_event.is_set(): while not self.record_stop_event.is_set():
file.write(self.record_queue.get()) file.write(self.record_queue.get())
# self.record_queue.task_done() # self.record_queue.task_done()
+1 -1
View File
@@ -36,7 +36,7 @@ class RobotConfig(draccus.ChoiceRegistry, abc.ABC):
) )
if hasattr(self, "microphones") and self.microphones: if hasattr(self, "microphones") and self.microphones:
for _, config in self.microphones.items(): for _, config in self.microphones.items():
for attr in ["sampling_rate", "channels"]: for attr in ["sample_rate", "channels"]:
if getattr(config, attr) is None: if getattr(config, attr) is None:
raise ValueError( raise ValueError(
f"Specifying '{attr}' is required for the microphone to be used in a robot" f"Specifying '{attr}' is required for the microphone to be used in a robot"
@@ -77,8 +77,7 @@ class KochFollower(Robot):
@property @property
def _microphones_ft(self) -> dict[str, tuple]: def _microphones_ft(self) -> dict[str, tuple]:
return { return {
mic: (self.config.microphones[mic].sampling_rate, self.config.microphones[mic].channels) mic: (self.config.microphones[mic].sample_rate, self.config.microphones[mic].channels) for mic in self.microphones
for mic in self.microphones
} }
@cached_property @cached_property
+1 -2
View File
@@ -102,8 +102,7 @@ class LeKiwi(Robot):
@property @property
def _microphones_ft(self) -> dict[str, tuple]: def _microphones_ft(self) -> dict[str, tuple]:
return { return {
mic: (self.config.microphones[mic].sampling_rate, self.config.microphones[mic].channels) mic: (self.config.microphones[mic].sample_rate, self.config.microphones[mic].channels) for mic in self.microphones
for mic in self.microphones
} }
@cached_property @cached_property
+1 -1
View File
@@ -99,7 +99,7 @@ class LeKiwiClient(Robot):
@cached_property @cached_property
def _microphones_ft(self) -> dict[str, tuple]: def _microphones_ft(self) -> dict[str, tuple]:
return {name: (cfg.sampling_rate, cfg.channels) for name, cfg in self.config.microphones.items()} return {name: (cfg.sample_rate, cfg.channels) for name, cfg in self.config.microphones.items()}
@cached_property @cached_property
def observation_features(self) -> dict[str, type | tuple]: def observation_features(self) -> dict[str, type | tuple]:
@@ -78,8 +78,7 @@ class SOFollower(Robot):
@property @property
def _microphones_ft(self) -> dict[str, tuple]: def _microphones_ft(self) -> dict[str, tuple]:
return { return {
mic: (self.config.microphones[mic].sampling_rate, self.config.microphones[mic].channels) mic: (self.config.microphones[mic].sample_rate, self.config.microphones[mic].channels) for mic in self.microphones
for mic in self.microphones
} }
@cached_property @cached_property