soundfile
SoundFile is a Python interface to the libsndfile C library. It reads sampled-audio files into NumPy arrays, writes arrays back to formats such as WAV, FLAC, OGG, MAT, and supported MP3 variants, streams large files in blocks, and exposes seekable file handles for frame-level access. CFFI bridges Python to libsndfile, while platform wheels usually carry the native library. It does file format I/O only: there is no resampling, decoding through FFmpeg, playback, feature extraction, effects graph, or audio-device API.
SoundFile is the clean default for NumPy audio I/O when libsndfile supports every required format. Choose an FFmpeg-based tool for broad codec coverage, and add a signal-processing library when reading and writing are only the first step.
Use it if
- You need simple NumPy-based reading and writing for formats supported by libsndfile
- You want block iteration or explicit frame seeking without loading an entire recording
- You need audio I/O from BytesIO, an uploaded file object, or another virtual file
- You want a thin file-format layer under signal-processing code you already own
- You need arbitrary media formats, video containers, or codecs outside libsndfile: this is not an FFmpeg wrapper
- You expect sample-rate conversion or channel remixing: read returns the stored sample rate and does not resample the data
- Your deployment platform lacks a compatible wheel and cannot install libsndfile: source installs require the native library from the OS
- You need shared-handle multithreading: the README warns that sharing readers or writers, or concurrently writing one file, can produce garbage or crashes
- You rely heavily on OGG writing without testing the exact native version: the README tracks a known issue where some libsndfile versions produce empty OGG files
Setup reality
SoundFile 0.14.0 requires Python 3.10 or newer and declares CFFI, NumPy, and typing-extensions as Python dependencies. The package itself has no compiled extension, but every operation depends on libsndfile. Wheels for common 64-bit and 32-bit Windows, Windows ARM64, Intel and ARM macOS, and 64-bit or AArch64 Linux include a current native library. On an unusual architecture, source build, minimal container, or distribution policy that forbids bundled native code, install libsndfile separately, such as libsndfile1 on Debian-family images. Since 0.12, packaged libsndfile is preferred over a system copy; install from source if using the system version is a deliberate requirement. Format support and bugs therefore depend on which native library actually loads, not only the Python package version. By default sf.read returns float64 and mono files are one-dimensional because always_2d defaults to False. Choose dtype and always_2d explicitly when downstream tensor shapes and memory matter. Integer reads do not normalize samples to floating point, and writing floats to an integer subtype quantizes them. RAW data has no header, so channels, sample rate, subtype, and sometimes endian must be supplied correctly. For long recordings use blocks or SoundFile.read with a frame count; a full-file read can allocate far more memory than the compressed file size suggests. Use a context manager so native handles close deterministically. File-like objects must be seekable for many formats, and in-memory output must be rewound before reading. Do not share a SoundFile handle between threads, and do not let multiple threads write the same file. Catch LibsndfileError for decoder failures and inspect code and error_string, while ordinary misuse may raise ValueError or TypeError. Pin and test the wheel/native combination for OGG writing and any codec that matters in production.
Patterns
Read samples and their stored rateread-audio
import soundfile as sf
audio, sample_rate = sf.read('voice.flac', dtype='float32', always_2d=True)
print(audio.shape, sample_rate)always_2d keeps mono as shape (frames, 1). Without it, mono input returns a one-dimensional array.
Write a WAV with an explicit subtypewrite-audio
import soundfile as sf
sf.write('output.wav', audio, sample_rate, subtype='PCM_16')Floating-point arrays are quantized when written to PCM_16. Choose a subtype deliberately instead of relying on the format default.
Convert a supported file to FLACcopy-to-flac
import soundfile as sf
data, rate = sf.read('input.wav', dtype='float32')
sf.write('output.flac', data, rate)This changes the container and encoding but does not resample or normalize the signal.
Inspect metadata without reading samplesinspect-file
import soundfile as sf
meta = sf.info('recording.wav')
print(meta.frames, meta.samplerate, meta.channels, meta.format, meta.subtype)Use info before allocating a full array so you can reject unexpected duration, channels, or sample rate.
Process a long file in overlapping blocksprocess-blocks
import numpy as np
import soundfile as sf
for block in sf.blocks('long.wav', blocksize=4096, overlap=1024, dtype='float32'):
rms = np.sqrt(np.mean(block ** 2, axis=0))
print(rms)Overlapping blocks repeat samples between iterations. Account for overlap when aggregating durations or statistics.
Read selected frames through a managed handleseek-and-read
import soundfile as sf
with sf.SoundFile('interview.flac') as f:
f.seek(10 * f.samplerate)
ten_seconds = f.read(10 * f.samplerate, dtype='float32', always_2d=True)Positions and counts are frames, not individual channel samples or bytes. The context manager closes the native handle.
Describe headerless RAW inputread-raw-audio
import soundfile as sf
data, rate = sf.read(
'capture.raw',
channels=1,
samplerate=48000,
subtype='PCM_16',
endian='LITTLE',
)RAW files cannot be auto-detected. Incorrect channels, subtype, rate, or endianness can yield plausible-looking but wrong samples.
Convert uploaded audio without a temporary fileconvert-in-memory
from io import BytesIO
import soundfile as sf
source = BytesIO(upload_bytes)
source.name = 'upload.ogg'
data, rate = sf.read(source)
out = BytesIO()
out.name = 'audio.wav'
sf.write(out, data, rate, format='WAV', subtype='PCM_16')
out.seek(0)
result_bytes = out.read()Rewind the output before reading. Supplying format is safer than depending on a file-like object's synthetic name.
Control supported lossy compressionset-compression
import soundfile as sf
sf.write(
'speech.mp3', audio, sample_rate,
bitrate_mode='VARIABLE',
compression_level=0.8,
)compression_level ranges from 0 to 1 and support depends on format plus the loaded libsndfile version.
Report native decoder errorshandle-decode-errors
import soundfile as sf
try:
audio, rate = sf.read(path)
except sf.LibsndfileError as exc:
print('libsndfile code:', exc.code)
print('decoder message:', exc.error_string)
raiseAPI misuse may raise ValueError or TypeError instead. Do not assume every failure is a LibsndfileError.
Check available formats and subtypescheck-format-support
import soundfile as sf
formats = sf.available_formats()
if 'FLAC' not in formats:
raise RuntimeError('This libsndfile build lacks FLAC support')
print(sf.available_subtypes('FLAC'))Capabilities come from the libsndfile instance loaded at runtime and can differ between wheel and system-library deployments.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| scipy | PyPI | You only need WAV I/O and already depend on SciPy for signal processing |
| pydub | PyPI | You want simple edits and broad format conversion and can install FFmpeg or another external backend |
| librosa | PyPI | You need resampling, analysis, features, and higher-level music or speech workflows on top of file loading |