fix(audio): share bit-depth-aware segment conversion
This commit is contained in:
@@ -65,18 +65,7 @@ def load_audio(audio_path: str, sampling_rate: int):
|
||||
# libraries are absent. The ``backend="soundfile"`` argument above
|
||||
# does NOT avoid that — 2.9 accepts and ignores it.
|
||||
aseg = AudioSegment.from_file(audio_path)
|
||||
# Scale by the decoded sample width instead of a hardcoded 16-bit
|
||||
# divisor. pydub reports 8-bit as sample_width 1 and widens 24-bit to
|
||||
# a full-range int32 (sample_width 4), so /32768 makes 24- and 32-bit
|
||||
# references 32768x too loud and 8-bit ones 256x too quiet.
|
||||
audio_data = (
|
||||
np.array(aseg.get_array_of_samples()).astype(np.float32)
|
||||
/ aseg.max_possible_amplitude
|
||||
)
|
||||
if aseg.channels == 1:
|
||||
waveform = torch.from_numpy(audio_data).unsqueeze(0)
|
||||
else:
|
||||
waveform = torch.from_numpy(audio_data.reshape(-1, aseg.channels).T)
|
||||
waveform = audiosegment_to_tensor(aseg)
|
||||
prompt_sampling_rate = aseg.frame_rate
|
||||
|
||||
if prompt_sampling_rate != sampling_rate:
|
||||
@@ -255,7 +244,7 @@ def audiosegment_to_tensor(aseg):
|
||||
audio_data = np.array(aseg.get_array_of_samples())
|
||||
|
||||
# Convert to float32 and normalize to [-1, 1] range
|
||||
audio_data = audio_data.astype(np.float32) / 32768.0
|
||||
audio_data = audio_data.astype(np.float32) / aseg.max_possible_amplitude
|
||||
|
||||
# Handle channels
|
||||
if aseg.channels == 1:
|
||||
|
||||
@@ -23,7 +23,6 @@ import pytest
|
||||
import soundfile as sf
|
||||
import torch
|
||||
|
||||
from omnivoice.utils.audio import load_audio
|
||||
|
||||
|
||||
def _write_sine_wav(path, *, seconds: float = 0.5, sample_rate: int = 24000):
|
||||
@@ -43,6 +42,7 @@ def _torchcodec_missing(*_a, **_kw):
|
||||
|
||||
def test_load_audio_falls_back_when_torchcodec_missing(tmp_path, monkeypatch):
|
||||
"""Without the ImportError catch this raises instead of returning audio."""
|
||||
from omnivoice.utils.audio import load_audio
|
||||
import torchaudio
|
||||
|
||||
ref = tmp_path / "ref.wav"
|
||||
@@ -60,6 +60,7 @@ def test_load_audio_falls_back_when_torchcodec_missing(tmp_path, monkeypatch):
|
||||
|
||||
def test_load_audio_fallback_resamples_to_target(tmp_path, monkeypatch):
|
||||
"""The fallback path must still honour the requested sampling rate."""
|
||||
from omnivoice.utils.audio import load_audio
|
||||
import torchaudio
|
||||
|
||||
ref = tmp_path / "ref_16k.wav"
|
||||
@@ -90,6 +91,7 @@ def test_load_audio_fallback_amplitude_matches_bit_depth(
|
||||
without TorchCodec it is the only path, which is what makes it a bug
|
||||
worth fixing here.
|
||||
"""
|
||||
from omnivoice.utils.audio import load_audio
|
||||
import torchaudio
|
||||
|
||||
sample_rate = 24000
|
||||
@@ -106,3 +108,14 @@ def test_load_audio_fallback_amplitude_matches_bit_depth(
|
||||
f"{subtype} decoded at the wrong scale: peak "
|
||||
f"{waveform.abs().max().item():.6f}, expected ~{peak}"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("subtype", ["PCM_U8", "PCM_16", "PCM_24", "PCM_32"])
|
||||
def test_audiosegment_conversion_preserves_amplitude(tmp_path, subtype):
|
||||
from pydub import AudioSegment
|
||||
from omnivoice.utils.audio import audiosegment_to_tensor
|
||||
path = tmp_path / "stereo.wav"
|
||||
samples = np.tile([0.5, -0.25], (100, 1))
|
||||
sf.write(path, samples, 24000, subtype=subtype)
|
||||
converted = audiosegment_to_tensor(AudioSegment.from_file(path)).numpy()
|
||||
np.testing.assert_allclose(converted, samples.T, atol=0.01)
|
||||
|
||||
Reference in New Issue
Block a user