mrkeyoor.com_
Sat 08 Aug 21:00 UTC
PyPIUtilsupdated 08 Aug 2026

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.

Verdict

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.

API stability4/5play, rec, playrec, wait, device queries, global defaults, ndarray streams, and raw streams form a mature API that has stayed familiar across releases. Version 0.5.5 adds platform fixes without redesigning ordinary use. Hardware and host-API behavior still vary outside the Python contract, and global convenience state can surprise code that expects concurrent independent operations.
Docs5/5The Read the Docs site separates installation, usage, convenience functions, streams, raw streams, devices, defaults, platform settings, examples, and version history. It explicitly explains bundled versus system PortAudio, conda overrides, Windows ASIO, callback deadlines, unpropagated exceptions, output-buffer assignment, blocksize and latency tradeoffs, and device selection. Those are the exact details audio wrappers often omit.
Maintenance5/5PyPI released 0.5.5 in January 2026, the repository was pushed in July 2026, and it is not archived. Current documentation covers modern packaging and the special Windows ASIO DLL behavior, while the code supports CFFI, NumPy and raw-buffer paths. The project is actively handling the unglamorous cross-platform driver and binary issues that determine whether an audio binding works.
Ecosystem4/5Millions of installs, NumPy interoperability, raw buffers, broad PortAudio host support, conda-forge and system packages, and practical examples make it a standard choice for Python audio I/O. It composes well with soundfile, SciPy, plotting, and signal-processing libraries. It intentionally does not cover codecs, editing, mixing, device-independent deployment, or a graphical audio engine.

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
Skip it if

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

PackageRegistryPick it when
PyAudioPyPIYou maintain code on the older PortAudio wrapper or need compatibility with its callback conventions
SoundCardPyPIYou prefer a simpler NumPy recording and playback API over native platform audio backends
pygamePyPIYou need straightforward sound playback as part of a game or multimedia application rather than low-level audio capture