sounddevice
sounddevice is a CFFI binding to PortAudio for playing and recording audio on Linux, macOS, and Windows. Its convenience functions move NumPy arrays to and from the default device, while InputStream, OutputStream, and Stream support callback or blocking real-time I/O. Raw stream variants accept plain buffer objects and do not require NumPy. The package exposes devices, host APIs, latency, channel, status, and platform-specific settings without trying to be an audio editor, codec library, or file-format reader.
A well-documented, direct route from Python arrays to real audio hardware. Use convenience calls for experiments and streams for products, but budget engineering time for PortAudio packaging, driver differences, and real-time callback discipline.
Use it if
- You need direct microphone and speaker I/O from Python with NumPy arrays
- You are building real-time processing, measurement, synthesis, or pass-through code on PortAudio-supported desktop systems
- You need callback streams, blocking read and write, device enumeration, channel mapping, latency controls, or raw buffers
- You want one Python API across Core Audio, WASAPI, WDM-KS, ASIO where enabled, ALSA, and other PortAudio host APIs
- You need to read, write, resample, or decode audio files: sounddevice only moves sample buffers to and from hardware, so pair it with soundfile or another codec library
- You cannot install or control PortAudio on Linux: pip bundles it on macOS and Windows, while other platforms may require a system libportaudio package
- Your callback needs file access, network requests, allocation-heavy NumPy work, locks, or unpredictable functions: the API reference forbids blocking work in the high-priority callback
- You need callback failures to reach the main thread automatically: ordinary callback exceptions print to stderr, stop further callbacks, and are not propagated
- You need guaranteed device names, channel counts, latency, or ASIO availability across machines: these come from the installed drivers and PortAudio build, not from Python code
Setup reality
python -m pip install sounddevice installs version 0.5.5 for Python 3.7 or newer and depends on CFFI. NumPy is optional in package metadata but required by play(), rec(), playrec(), and the ndarray stream classes; install the numpy extra or NumPy separately. RawInputStream, RawOutputStream, and RawStream work with Python buffers instead. On pip-installed macOS and Windows, PortAudio is bundled. Linux and other platforms may need a system package such as libportaudio2, development packages for some build paths, and working ALSA, PulseAudio, PipeWire, JACK, or other host configuration. A package-manager or conda PortAudio can override the pip-bundled library, which changes available host APIs and behavior. Windows ASIO is particularly conditional: pip ships DLLs with and without ASIO, loads the non-ASIO build by default, and requires SD_ENABLE_ASIO to be set before importing sounddevice. That switch does not work with the conda package and is bypassed by a custom portaudio.dll earlier on PATH. Start every deployment by logging query_hostapis() and query_devices(), then select an input and output device explicitly. Numeric IDs can change when hardware is reconnected; case-insensitive device-name substrings are friendlier but can become ambiguous. Call check_input_settings() and check_output_settings() for the exact sample rate, channels, and dtype before opening a long-lived stream. Convenience calls return immediately unless blocking=True or followed by wait(), and each new play(), rec(), or playrec() stops the previous convenience operation, so they are for scripts and notebooks rather than a mixer. The default recording dtype is float32, sample rate must match the intended audio or playback speed and pitch change, and input and output channel counts need explicit thought. Real-time callbacks are the largest trap. PortAudio calls them at high priority; they must fill every output frame, should not allocate memory, touch the filesystem, acquire contended locks, call unpredictable library functions, or invoke most PortAudio APIs. Assign outdata[:] rather than rebinding outdata. Inspect the status flags for underflow and overflow. Ordinary callback exceptions are printed to stderr and never reach the main thread, so communicate failures through a thread-safe queue or event and raise CallbackStop or CallbackAbort deliberately. blocksize=0 lets the host choose varying block sizes and is upstream's preferred setting under heavy callback load; a fixed block size can add buffering and latency. The default high latency is safer but may be unsuitable for interactive work, while low latency increases glitch risk. Audio tests must run on the actual hardware and driver stack, because CI machines often have no device or a different default.
Patterns
Inspect devices and host APIslist-audio-devices
import sounddevice as sd
print(sd.query_hostapis())
for index, device in enumerate(sd.query_devices()):
print(index, device['name'], device['max_input_channels'], device['max_output_channels'])Device indexes can change across reboots or reconnection; persist a verified name or stable application choice rather than assuming an index.
Check a format before opening a streamvalidate-device-settings
import sounddevice as sd
sd.check_input_settings(
device='USB Audio',
channels=1,
samplerate=48000,
dtype='float32',
)
sd.check_output_settings(
device='USB Audio',
channels=2,
samplerate=48000,
dtype='float32',
)Name fragments are case-insensitive but must identify a device unambiguously; query devices when selection fails.
Play a NumPy array to completionplay-array
import sounddevice as sd
sd.play(samples, samplerate=48000)
status = sd.wait()
if status:
print(status)play returns immediately; wait blocks and reports callback status such as an output underflow.
Record a fixed-duration mono cliprecord-array
import sounddevice as sd
samplerate = 48000
duration = 5
audio = sd.rec(
int(duration * samplerate),
samplerate=samplerate,
channels=1,
dtype='float32',
)
sd.wait()rec is nonblocking by default and float32 is the default recording dtype; call wait before consuming the complete array.
Play a signal while recording inputplay-and-record
import sounddevice as sd
recorded = sd.playrec(
output_samples,
samplerate=48000,
channels=1,
dtype='float32',
)
sd.wait()Output channels come from the playback array, while channels specifies the number of recorded input channels.
Configure process-wide defaultsset-audio-defaults
import sounddevice as sd
sd.default.device = ('USB Microphone', 'Built-in Output')
sd.default.samplerate = 48000
sd.default.channels = (1, 2)
sd.default.dtype = ('float32', 'float32')
sd.default.latency = ('high', 'low')Defaults are module-wide mutable state; explicit stream arguments are safer inside reusable libraries.
Pass microphone input to outputcreate-duplex-stream
import sounddevice as sd
status_flag = [None]
def callback(indata, outdata, frames, time, status):
if status:
status_flag[0] = status
outdata[:] = indata
with sd.Stream(
samplerate=48000,
channels=1,
dtype='float32',
blocksize=0,
callback=callback,
):
sd.sleep(5000)
if status_flag[0]:
print(status_flag[0])Keep the callback bounded and nonblocking, and use headphones to avoid acoustic feedback between microphone and speakers.
Stop a stream deliberately from its callbackstop-callback-stream
import sounddevice as sd
frame_index = 0
def callback(outdata, frames, time, status):
global frame_index
count = min(frames, len(samples) - frame_index)
outdata.fill(0)
outdata[:count] = samples[frame_index:frame_index + count]
frame_index += count
if count < frames:
raise sd.CallbackStop
with sd.OutputStream(samplerate=48000, channels=2, callback=callback):
sd.sleep(10000)Always fill the entire output buffer before CallbackStop; ordinary exceptions print to stderr and do not propagate to the main thread.
Read audio in blocking modeblocking-stream-read
import sounddevice as sd
with sd.InputStream(samplerate=48000, channels=1, dtype='float32') as stream:
block, overflowed = stream.read(1024)
if overflowed:
print('input overflow')
process(block)Omitting callback selects blocking read and write mode; keep draining the stream fast enough to avoid overflows.
Opt into the bundled ASIO PortAudio DLLenable-windows-asio
import os
os.environ['SD_ENABLE_ASIO'] = '1'
import sounddevice as sd
print(sd.query_hostapis())Set the variable before the first sounddevice import; this works only with the pip package and not conda's PortAudio build.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| PyAudio | PyPI | You maintain code on the older PortAudio wrapper or need compatibility with its callback conventions |
| SoundCard | PyPI | You prefer a simpler NumPy recording and playback API over native platform audio backends |
| pygame | PyPI | You need straightforward sound playback as part of a game or multimedia application rather than low-level audio capture |