-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Expand file tree
/
Copy pathtest_audio_io.py
More file actions
288 lines (211 loc) · 8.86 KB
/
Copy pathtest_audio_io.py
File metadata and controls
288 lines (211 loc) · 8.86 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
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
"""Tests for audio_io module.
Authors
* SpeechBrain Contributors 2025
"""
import os
import numpy as np
import pytest
import torch
def test_audio_io_roundtrip_wav(tmpdir):
"""Test save and load roundtrip for WAV format."""
from speechbrain.dataio import audio_io
# Create a simple sine wave test signal
sample_rate = 16000
duration = 1.0 # seconds
frequency = 440.0 # Hz (A4 note)
t = torch.linspace(0, duration, int(sample_rate * duration))
waveform = torch.sin(2 * np.pi * frequency * t)
waveform = waveform.unsqueeze(0) # Add channel dimension: (1, frames)
# Save audio
audio_path = os.path.join(tmpdir, "test.wav")
audio_io.save(audio_path, waveform, sample_rate)
# Load audio back
loaded_waveform, loaded_sr = audio_io.load(audio_path, channels_first=True)
# Check sample rate
assert loaded_sr == sample_rate, (
f"Expected sample rate {sample_rate}, got {loaded_sr}"
)
# Check shape
assert loaded_waveform.shape[0] == 1, (
f"Expected 1 channel, got {loaded_waveform.shape[0]}"
)
# Check values are close (allow for encoding/decoding differences)
assert torch.allclose(loaded_waveform, waveform, atol=1e-3), (
"Waveforms don't match"
)
def test_audio_io_roundtrip_flac(tmpdir):
"""Test save and load roundtrip for FLAC format."""
from speechbrain.dataio import audio_io
# Create a test signal
sample_rate = 22050
waveform = torch.rand(1, 22050) # 1 second of random noise
# Save as FLAC
audio_path = os.path.join(tmpdir, "test.flac")
audio_io.save(audio_path, waveform, sample_rate, subtype="PCM_16")
# Load back
loaded_waveform, loaded_sr = audio_io.load(audio_path)
# Check sample rate
assert loaded_sr == sample_rate
# Check shape
assert loaded_waveform.shape == waveform.shape
# Check values are reasonably close
assert torch.allclose(loaded_waveform, waveform, atol=5e-3)
def test_audio_io_info(tmpdir):
"""Test info function returns expected metadata."""
from speechbrain.dataio import audio_io
# Create test audio
sample_rate = 16000
duration = 2.5 # seconds
num_frames = int(sample_rate * duration)
waveform = torch.rand(1, num_frames)
# Save audio
audio_path = os.path.join(tmpdir, "test_info.wav")
audio_io.save(audio_path, waveform, sample_rate)
# Get info
info = audio_io.info(audio_path)
# Check metadata
assert info.sample_rate == sample_rate, (
f"Expected sample rate {sample_rate}, got {info.sample_rate}"
)
assert info.frames == num_frames, (
f"Expected {num_frames} frames, got {info.frames}"
)
assert info.num_frames == num_frames, "num_frames alias doesn't match"
assert info.channels == 1, f"Expected 1 channel, got {info.channels}"
assert info.num_channels == 1, "num_channels alias doesn't match"
assert abs(info.duration - duration) < 0.01, (
f"Expected duration ~{duration}s, got {info.duration}s"
)
assert info.format == "WAV", f"Expected format WAV, got {info.format}"
def test_audio_io_load_channels_first(tmpdir):
"""Test load with channels_first=True."""
from speechbrain.dataio import audio_io
# Create stereo audio
sample_rate = 16000
waveform = torch.rand(2, 8000) # (channels, frames)
audio_path = os.path.join(tmpdir, "stereo.wav")
audio_io.save(audio_path, waveform, sample_rate)
# Load with channels_first=True (default)
loaded, sr = audio_io.load(audio_path, channels_first=True)
assert loaded.shape == (2, 8000), f"Expected (2, 8000), got {loaded.shape}"
# Load with channels_first=False
loaded_cf, sr = audio_io.load(audio_path, channels_first=False)
assert loaded_cf.shape == (8000, 2), (
f"Expected (8000, 2), got {loaded_cf.shape}"
)
def test_audio_io_load_always_2d(tmpdir):
"""Test load with always_2d parameter."""
from speechbrain.dataio import audio_io
# Create mono audio
sample_rate = 16000
waveform = torch.rand(16000) # 1D mono
audio_path = os.path.join(tmpdir, "mono.wav")
audio_io.save(audio_path, waveform, sample_rate)
# Load with always_2d=True, channels_first=True
loaded, sr = audio_io.load(audio_path, channels_first=True, always_2d=True)
assert loaded.shape == (1, 16000), (
f"Expected (1, 16000), got {loaded.shape}"
)
# Load with always_2d=True, channels_first=False
loaded_cf, sr = audio_io.load(
audio_path, channels_first=False, always_2d=True
)
assert loaded_cf.shape == (16000, 1), (
f"Expected (16000, 1), got {loaded_cf.shape}"
)
def test_audio_io_save_shapes(tmpdir):
"""Test save handles various input shapes correctly."""
from speechbrain.dataio import audio_io
sample_rate = 16000
# Test 1D input (mono)
waveform_1d = torch.rand(8000)
path_1d = os.path.join(tmpdir, "mono_1d.wav")
audio_io.save(path_1d, waveform_1d, sample_rate)
loaded_1d, _ = audio_io.load(path_1d, channels_first=True, always_2d=True)
assert loaded_1d.shape == (1, 8000)
# Test 2D input channels-first (channels, frames)
waveform_2d = torch.rand(1, 8000)
path_2d = os.path.join(tmpdir, "mono_2d.wav")
audio_io.save(path_2d, waveform_2d, sample_rate)
loaded_2d, _ = audio_io.load(path_2d, channels_first=True, always_2d=True)
assert loaded_2d.shape == (1, 8000)
def test_audio_io_save_stereo(tmpdir):
"""Test save and load stereo audio."""
from speechbrain.dataio import audio_io
sample_rate = 16000
waveform = torch.rand(2, 8000) # Stereo (2 channels)
audio_path = os.path.join(tmpdir, "stereo.wav")
audio_io.save(audio_path, waveform, sample_rate)
loaded, sr = audio_io.load(audio_path, channels_first=True)
assert loaded.shape == (2, 8000), (
f"Expected stereo (2, 8000), got {loaded.shape}"
)
assert torch.allclose(loaded, waveform, atol=1e-3)
def test_audio_io_dtype(tmpdir):
"""Test load with different dtype."""
from speechbrain.dataio import audio_io
sample_rate = 16000
waveform = torch.rand(1, 8000)
audio_path = os.path.join(tmpdir, "test_dtype.wav")
audio_io.save(audio_path, waveform, sample_rate)
# Load with float64
loaded_f64, _ = audio_io.load(audio_path, dtype=torch.float64)
assert loaded_f64.dtype == torch.float64
# Load with float32 (default)
loaded_f32, _ = audio_io.load(audio_path, dtype=torch.float32)
assert loaded_f32.dtype == torch.float32
def test_audio_io_numpy_input(tmpdir):
"""Test save with numpy array input."""
from speechbrain.dataio import audio_io
sample_rate = 16000
waveform_np = np.random.rand(1, 8000).astype(np.float32)
audio_path = os.path.join(tmpdir, "numpy_input.wav")
audio_io.save(audio_path, waveform_np, sample_rate)
loaded, sr = audio_io.load(audio_path, channels_first=True)
assert loaded.shape == (1, 8000)
assert torch.allclose(loaded, torch.from_numpy(waveform_np), atol=1e-3)
def test_audio_io_save_integer_tensor_pcm_roundtrip(tmpdir):
"""Test save preserves integer tensor values for PCM output."""
from speechbrain.dataio import audio_io
sample_rate = 16000
waveform = torch.tensor([[0, 1, -1, 255, -255]], dtype=torch.int64)
audio_path = os.path.join(tmpdir, "integer_input.wav")
audio_io.save(audio_path, waveform, sample_rate, subtype="PCM_16")
loaded, sr = audio_io.load(
audio_path,
channels_first=True,
always_2d=True,
dtype=torch.int16,
)
assert sr == sample_rate
assert torch.equal(loaded, waveform.to(torch.int16))
def test_audio_io_list_backends():
"""Test list_audio_backends function."""
from speechbrain.dataio import audio_io
backends = audio_io.list_audio_backends()
assert isinstance(backends, list)
assert "soundfile" in backends
def test_audio_io_error_handling(tmpdir):
"""Test error handling for invalid inputs."""
from speechbrain.dataio import audio_io
# Test loading non-existent file
with pytest.raises(RuntimeError, match="Failed to load"):
audio_io.load(os.path.join(tmpdir, "nonexistent.wav"))
# Test info on non-existent file
with pytest.raises(RuntimeError, match="Failed to get info"):
audio_io.info(os.path.join(tmpdir, "nonexistent.wav"))
def test_audio_io_different_subtypes(tmpdir):
"""Test saving with different audio subtypes."""
from speechbrain.dataio import audio_io
sample_rate = 16000
waveform = torch.rand(1, 8000)
# Test PCM_16 (default)
path_16 = os.path.join(tmpdir, "pcm16.wav")
audio_io.save(path_16, waveform, sample_rate, subtype="PCM_16")
info_16 = audio_io.info(path_16)
assert info_16.subtype == "PCM_16"
# Test PCM_24
path_24 = os.path.join(tmpdir, "pcm24.wav")
audio_io.save(path_24, waveform, sample_rate, subtype="PCM_24")
info_24 = audio_io.info(path_24)
assert info_24.subtype == "PCM_24"