---
name: matlab/matlab-play-record-audio
source: https://app.decimal.ai/s/matlab-matlab-play-record-audio@1/SKILL.md
source_sha256: 64bfa283c51b
---

# audiostreamer — MATLAB Audio Device I/O (R2025a+)

`audiostreamer` is the unified replacement for `audioDeviceWriter`, `audioDeviceReader`, and `audioPlayerRecorder`. It provides player-only, recorder-only, or full-duplex modes with callbacks, pre-buffering, transport control, and measurement helpers.

**Version requirements:** `audiostreamer` requires Audio Toolbox R2025a or later. The `start` and `write` methods were added in R2026a.

## When to Use

- Playing audio through a sound card or USB audio device
- Recording audio from a microphone or audio interface
- Full-duplex playback + recording (e.g., acoustic measurements, loopback tests)
- Listing or selecting audio devices and drivers
- Any workflow that involves audio hardware I/O in MATLAB

## When NOT to Use

- **Playing a single isolated sound** — `sound` or `soundsc` is fine for one-shot playback of a short clip with no sequencing. For sequential playback (e.g., before/after comparison), use `audiostreamer` — its `play()` calls queue automatically, whereas overlapping `sound` calls play simultaneously.
- **Code generation (codegen)** — `audiostreamer` does not yet support codegen; use legacy APIs if targeting codegen
- **Simulink models** — Simulink still uses the existing audio I/O blocks, not `audiostreamer`
- **Audio Toolbox not available** — fall back to `sound`/`soundsc`, `audioplayer`/`audiorecorder`, or `audioDeviceWriter` (DSP System Toolbox) if the user lacks Audio Toolbox
- **File I/O only** — reading/writing audio files without device playback or recording uses `audioread`/`audiowrite`, not this skill
- **DAQ hardware** — National Instruments or similar data acquisition devices use DAQ Toolbox and the `daq` object
- **MIDI-only devices** — MIDI control uses `mididevice`/`midicontrols`, not `audiostreamer`

## Construction

Use Name-Value pairs for `Mode` and `SampleRate` (positional shorthand exists but does not support tab-completion):

**The default Mode is `"player"`. You MUST set Mode explicitly if recording** — either `Mode="recorder"` or `Mode="full-duplex"`. Mode is not inferred from other properties like `Recorder` or `RecorderChannels`.

```matlab
as = audiostreamer                                          % default: player mode, 44100 Hz
as = audiostreamer(Mode="player", SampleRate=fs)            % player at fs Hz
as = audiostreamer(Mode="recorder", SampleRate=fs)          % recorder at fs Hz
as = audiostreamer(Mode="full-duplex", SampleRate=fs)       % simultaneous play + record
as = audiostreamer(Mode="full-duplex", SampleRate=48000, Driver="ASIO", ...
    Player="Focusrite USB ASIO", Recorder="Focusrite USB ASIO", ...
    PlayerChannels=[1 2], RecorderChannels=[1 2])
```

## Properties

### Device Configuration (set BEFORE streaming starts)

| Property | Type | Default | Notes |
|----------|------|---------|-------|
| `Mode` | `"player"` / `"recorder"` / `"full-duplex"` | `"player"` | Set at construction or via property |
| `Driver` | `"DirectSound"` / `"ASIO"` / `"WASAPI"` (Win); `"CoreAudio"` (Mac); `"ALSA"` (Linux) | OS default | Only set on Windows (Mac/Linux have one driver each). Setting at construction selects the default device for that driver. |
| `Player` | string | System default for driver | Output device name. Omit to use the default device for the selected driver. |
| `Recorder` | string | System default for driver | Input device name. Omit to use the default device for the selected driver. |
| `SampleRate` | positive scalar | 44100 | Hz |
| `DeviceBufferSize` | positive int or `"auto"` | `"auto"` | Fixed for ASIO (use `asiosettings`). |
| `DeviceBitFormat` | `"single"` / `"int24"` / `"int16"` | `"int24"` | int16 on ASIO silently uses int24 |
| `PlayerChannels` | row vector or `"auto"` | `"auto"` | 1-based mapping. "auto" upmixes mono→stereo; for N≥2 channels, opens N channels on the device |
| `RecorderChannels` | row vector | `1` | 1-based mapping. **Records 1 channel by default** — set e.g. `1:2` for stereo |
| `ExclusiveMode` | on/off | `"on"` | WASAPI only — disables OS mixing/resampling |
| `ConstantLatency` | `"off"` / `"dropPlayer"` / `"dropRecorder"` | `"off"` | Full-duplex dropout handling |

**IMPORTANT:** `Mode`, `SampleRate`, `Driver`, `DeviceBufferSize`, `DeviceBitFormat`, `ExclusiveMode`, `ConstantLatency`, `PlayerChannels`, and `RecorderChannels` lock once streaming starts. Call `release(as)` before changing any of these properties to avoid an automatic release with a warning.

### Callback Properties

| Property | Signature | Trigger |
|----------|-----------|---------|
| `PlayerFcn` | `@(obj, event)` | Player buffer drops below `PlayerMinSamples` |
| `PlayerMinSamples` | positive int (default 16384) | Threshold for `PlayerFcn` trigger |
| `RecorderFcn` | `@(obj, event)` | Recorder buffer exceeds `RecorderMinSamples` |
| `RecorderMinSamples` | positive int (default 1024) | Threshold for `RecorderFcn` trigger |
| `PlayerCompletedFcn` | `@(obj, event)` | Output queue empties |
| `RecorderCompletedFcn` | `@(obj, event)` | Fixed-length recording finishes |
| `PlayerUnderrunFcn` | `@(obj, event)` | Player underrun occurs |

**ALL callbacks MUST accept exactly 2 arguments.** First arg = the audiostreamer object. Second arg = event struct with `.Type` field. Use `@(obj, ~)` if you don't need the event.

Event struct fields by type:
- `PlayerFcn`: `event.Type = "Player"`, `event.NumPlayerSamples`
- `RecorderFcn`: `event.Type = "Recorder"`, `event.NumRecorderSamples`
- `PlayerCompletedFcn`: `event.Type = "PlayerCompleted"`, `event.StreamTime`
- `RecorderCompletedFcn`: `event.Type = "RecorderCompleted"`, `event.StreamTime`
- `PlayerUnderrunFcn`: `event.Type = "PlayerUnderrun"`, `event.SamplesUnderrun`

### Read-Only Status

| Property | Description |
|----------|-------------|
| `NumPlayerSamples` | Samples currently queued in output buffer |
| `NumRecorderSamples` | Samples available to `read()` without blocking |
| `MaxPlayerChannels` | Max output channels on selected device |
| `MaxRecorderChannels` | Max input channels on selected device |

## Methods

### Playback

| Method | Description |
|--------|-------------|
| `play(obj, x)` | Queue `x` and play. **Blocks** until output buffer <= `PlayerMinSamples` (up to `PlayerMinSamples` samples remain unplayed when it returns). Call `waitfor(as)` after the last `play` to ensure complete playback before `release`. |
| `play(obj, x, "non-blocking")` | Queue `x` and return immediately regardless of buffer level. |
| `play(obj)` | Start PlayerFcn callback loop (no data argument). |
| `write(obj, x)` | [R2026a+] Queue `x` to output buffer WITHOUT starting playback. Use with `start()`. |
| `write(obj, x, "non-blocking")` | [R2026a+] Queue `x` and return immediately regardless of buffer level. |

### Recording

| Method | Description |
|--------|-------------|
| `record(obj)` | Start recording indefinitely. Warns if unread samples remain in the buffer. To avoid: `stop(as)` (or `stop(as, "recorder")` in full-duplex), then `read(as)` to flush. Not needed if samples were already consumed by a callback or `read`. |
| `record(obj, numSamples)` | Record exactly `numSamples` then stop. Same unread-samples warning applies. |
| `read(obj)` | Return all available recorded samples immediately (non-blocking). Returns empty if none available. |
| `read(obj, numSamples)` | **Blocks** until `numSamples` available, then returns them. |

### Full-Duplex

| Method | Description |
|--------|-------------|
| `playrec(obj, x)` | Play `x` and record simultaneously. Non-blocking — recording continues in the background; retrieve data with `read`. |
| `playrec(obj, x, numSamples)` | Play `x` and record `numSamples`. Blocking — returns recorded matrix. |
| `playrec(obj)` | Start callback-driven full-duplex (requires RecorderFcn and/or PlayerFcn). |

`playrec` pauses both player and recorder, queues audio, then resumes both simultaneously for **repeatable latency**. This is critical for measurements with `impzest`.

### Transport Control

| Method | Description |
|--------|-------------|
| `start(obj)` | [R2026a+] Start streaming in current mode. |
| `start(obj, Mode="player")` | [R2026a+] Start only player (full-duplex). |
| `start(obj, Mode="recorder", SamplesToRecord=N)` | [R2026a+] Start recorder with fixed count. |
| `stop(obj)` | Stop all streaming. Preserves unread input samples. Resets underrun count (as does `getUnderrunCount`). |
| `stop(obj, "player"/"recorder"/"both")` | Stop specific side. |
| `pause(obj)` / `pause(obj, "player"/"recorder"/"both")` | Pause with state preservation. |
| `resume(obj)` / `resume(obj, "player"/"recorder"/"both")` | Resume from pause. |
| `waitfor(obj)` / `waitfor(obj, "player"/"recorder"/"both")` | Block until complete. |
| `release(obj)` | Stop, flush, close device, tear down. Deletes unread samples. |

### Query / Diagnostics

| Method | Description |
|--------|-------------|
| `isPlaying(obj)` | Returns OnOffSwitchState |
| `isRecording(obj)` | Returns OnOffSwitchState |
| `isPlayerPaused(obj)` | Returns OnOffSwitchState |
| `isRecorderPaused(obj)` | Returns OnOffSwitchState |
| `getUnderrunCount(obj)` | Underrun sample count since last call. Resets counter (as does `stop`). |
| `getStreamTime(obj)` | Elapsed stream time in seconds. |
| `getStreamTime(obj, "reset")` | Reset stream timer. |
| `measureLoopbackLatency(obj)` | Full-duplex only, single channel. Returns delay in samples. |

### Static Device Enumeration

```matlab
audiostreamer.getDrivers()              % Available drivers for this OS
audiostreamer.getPlayerNames()          % All output devices
audiostreamer.getPlayerNames("ASIO")    % Output devices for specific driver
audiostreamer.getRecorderNames()        % All input devices
audiostreamer.getRecorderNames("ASIO")  % Input devices for specific driver
audiostreamer.getAudioDevices()         % Struct array: Name, Driver, MaxRecorderChannels, MaxPlayerChannels, SampleRate (channel counts are int32)
```

**Note:** `getAudioDevices()` returns `int32` for `MaxRecorderChannels` and `MaxPlayerChannels`. Cast to `double()` before using these values in UI components (e.g., `uispinner` Limits) or arithmetic that expects double.

## CRITICAL: There is NO `setup()` Method

The `audiostreamer` does NOT have a public `setup()` method. Device initialization happens implicitly on the first `play()`, `record()`, `playrec()`, or `start()` call. Do NOT call `setup()` — it will error.

If `PlayerFcn` is set, the first streaming call invokes it repeatedly to pre-buffer at least 8192 samples (or `PlayerMinSamples`, whichever is greater) before the device opens.

## Common Patterns

### Pattern 1: Simple Blocking Measurement (Sweep + IR)

```matlab
as = audiostreamer(Mode="full-duplex", SampleRate=48000, ...
    PlayerChannels=1, RecorderChannels=1);
x = sweeptone(2, 1, 48000);
y = playrec(as, x, size(x, 1));  % blocking: returns recorded audio
underruns = getUnderrunCount(as);
ir = impzest(x, y);
release(as);
```

### Pattern 2: Non-Blocking Play + Record with waitfor

```matlab
as = audiostreamer(Mode="full-duplex", SampleRate=48000);
x = sweeptone(3, 2, 48000);
playrec(as, x);          % non-blocking (no output arg)
waitfor(as);             % block until done
y = read(as);            % retrieve recorded data
release(as);
```

### Pattern 3: Pre-Buffered Playback (write + start) — R2026a+

Use `write`+`start` when you need to control exactly when playback begins (e.g., synchronized full-duplex start). For simple playback, `play(as, signal)` achieves the same result — it queues and starts automatically.

```matlab
as = audiostreamer(Mode="player", SampleRate=48000);
write(as, signal);       % queue without starting
start(as);               % begin playback
waitfor(as);             % wait for completion
release(as);
```

### Pattern 4: Callback-Driven Streaming Player

```matlab
as = audiostreamer(Mode="player", SampleRate=48000, DeviceBufferSize=1024);
gen = dsp.ColoredNoise("pink", NumChannels=2, SamplesPerFrame=1024);
as.PlayerFcn = @(obj, ~) play(obj, gen());
as.PlayerMinSamples = 4096;
play(as);    % or start(as) [R2026a+] — begins callback loop
% ... later ...
stop(as);
release(as);
```

### Pattern 5: Callback-Driven Level Metering (Recorder)

```matlab
as = audiostreamer(Mode="recorder", SampleRate=48000);
as.RecorderFcn = @(obj, ~) updateMeter(read(obj));
as.RecorderMinSamples = 1024;
start(as);       % or record(as) before R2026a
% ... meter updates in background ...
stop(as);
release(as);
```

### Pattern 6: Frame-at-a-Time Processing Loop (Full-Duplex)

The most direct replacement for legacy `audioDeviceReader`/`audioDeviceWriter` loops. Call `start(as)` before the loop so that `read` has samples available.

```matlab
as = audiostreamer(Mode="full-duplex", SampleRate=48000, RecorderChannels=1:2);
start(as);
for iter = 1:numIterations
    in = read(as, frameLength);      % blocks until frameLength samples available
    out = process(myPlugin, in);
    write(as, out);                  % blocks until buffer has room
end
nUnderruns = getUnderrunCount(as);   % total underruns since last call (resets counter)
release(as);
```

Before R2026a, use `record(as)` + `play(as, out)` instead of `start`/`write`.

For player-only (e.g., file input → device output), use `play(as, out)` with no `start` needed — `play` queues and starts automatically.

### Pattern 7: Repeated Measurements with Callbacks (Full-Duplex)

```matlab
as = audiostreamer(Mode="full-duplex", SampleRate=48000, PlayerChannels=1, RecorderChannels=1);
x = sweeptone(2, 1, 48000);
as.RecorderMinSamples = size(x, 1);
as.RecorderFcn = @(obj, ~) processMeasurement(obj, x);
as.PlayerFcn = @(obj, ~) write(obj, x);  % or play(obj, x) before R2026a
as.PlayerMinSamples = size(x, 1);
as.ConstantLatency = "dropPlayer";  % keep in sync for impzest
playrec(as);   % starts callback-driven measurement loop
% ... runs continuously ...
stop(as);
release(as);
```

### Pattern 8: App with Timer-Based GUI Updates

```matlab
as = audiostreamer(Mode="player", SampleRate=fs, DeviceBufferSize=1024);
as.PlayerFcn = @(obj, ~) play(obj, getNextFrame());
as.PlayerMinSamples = 20 * 1024;
as.PlayerUnderrunFcn = @(~, ev) fprintf("Dropped %d samples\n", ev.SamplesUnderrun);

figTimer = timer(ExecutionMode="fixedRate", Period=0.05, ...
    TimerFcn=@(~,~) updatePlot(as));

play(as);           % starts callback loop
start(figTimer);    % starts GUI updates
% ...
stop(as);
release(as);
stop(figTimer);
delete(figTimer);
```

In the timer callback, check buffer health before expensive GUI operations:
```matlab
function updatePlot(as)
    if as.NumPlayerSamples < 0.5 * as.PlayerMinSamples
        return  % skip GUI update to prevent dropout
    end
    % ... update plots ...
    if as.NumPlayerSamples > 0.9 * as.PlayerMinSamples
        drawnow("limitrate");
    end
end
```

### Pattern 9: Full-Duplex with write/start for Control — R2026a+

```matlab
as = audiostreamer(Mode="full-duplex", SampleRate=48000);
write(as, excitation);                         % queue output
start(as, SamplesToRecord=size(excitation,1)); % start both
waitfor(as, "both");
y = read(as);
release(as);
```

## Teardown Best Practice

`release(as)` is sufficient — it implicitly stops streaming, flushes buffers, and closes the device. No need to call `stop` first. However, `release` discards any unplayed samples — call `waitfor(as)` first if playback must complete.

```matlab
waitfor(as);   % ensure all queued audio finishes playing
release(as);
```

In apps, wrap in try-catch and nil the reference:
```matlab
try
    release(as);
catch
end
as = [];
```

The destructor calls `release()` automatically, but explicit cleanup is preferred in apps to avoid device lock-up. Calling `release` from within `PlayerCompletedFcn` is safe and does not deadlock.

**Note:** `isvalid(as)` returns `true` even after release — it cannot be used to detect a released audiostreamer. To track released state, nil the object reference and check with `isempty`.

## ConstantLatency Modes (Full-Duplex)

| Value | Behavior | Use For |
|-------|----------|---------|
| `"off"` | After dropout, inserts silence frame (latency increases) | General use |
| `"dropPlayer"` | Late output frames dropped; latency stays constant | Measurements with `impzest` (sweep-based) |
| `"dropRecorder"` | Input frames dropped; latency constant | Adaptive filters (NOT compatible with `impzest`) |

## Error Conditions

| Error ID | Cause |
|----------|-------|
| `audio:device:methodRequiresModes` | Calling method invalid for current Mode (e.g., `record()` in player mode). Set Mode to `"full-duplex"` if you need both playback and recording methods. |
| `audio:device:invalidChannelMap` | Channel indices exceed device max. Check `MaxPlayerChannels` or `MaxRecorderChannels` and adjust mapping. |
| `audio:device:callbackNargin` | Callback doesn't accept exactly 2 arguments. Use `@(obj, ~)` or `@(obj, event)` signature. |
| `audio:device:playrecRecorderFcnConflict` | `playrec` called with output argument while `RecorderFcn` is set — callback consumes samples via `read()`, leaving nothing for the return value. Clear `RecorderFcn` before blocking `playrec`. |
| `audio:device:startModePlayerNotValid` | `start(Mode="player")` in recorder-only mode |
| `audio:device:startModeRecorderNotValid` | `start(Mode="recorder")` in player-only mode |
| `MATLAB:validators:mustBeFinite` | Audio data contains NaN or Inf |
| `MATLAB:validators:mustBeReal` | Audio data is complex |

## audiostreamer vs. Legacy Audio APIs

`audiostreamer` is strongly preferred for all audio device I/O when Audio Toolbox is available. Legacy alternatives may be useful as fallbacks when Audio Toolbox is not installed or in edge cases.

| Legacy API | Limitation | audiostreamer Equivalent |
|------------|-----------|-----------|
| `audiodevinfo` | Does not support ASIO; incomplete device list | `audiostreamer.getAudioDevices()`, `audiostreamer.getPlayerNames()`, etc. |
| `audioplayer` / `audiorecorder` | No ASIO/WASAPI exclusive; limited driver model; no callbacks | `audiostreamer` in player/recorder/full-duplex mode |
| `sound` / `soundsc` | Creates an `audioplayer` under the hood; concurrent calls overlap (do NOT queue) | `audiostreamer` with `play()` for sequential playback |
| `audioDeviceWriter` / `audioDeviceReader` | Separate objects; no callbacks; no pre-buffering; frame-at-a-time loops only | Single `audiostreamer` object with blocking/non-blocking modes |
| `audioPlayerRecorder` | Limited full-duplex; no transport control; no latency measurement | `playrec`, `measureLoopbackLatency`, start/stop/pause/resume |

**`audiodevreset`** is fine to call — it resets the audio subsystem and can help recover from device errors regardless of which API you use.

**When `sound`/`soundsc` is acceptable:** Only for a single isolated playback with no sequencing. If you need to play two clips back-to-back (e.g., before/after comparison), use `audiostreamer` — its `play()` calls queue automatically.

### Migration Pitfalls (audioDeviceReader/Writer → audiostreamer)

| Legacy | audiostreamer | Gotcha |
|--------|--------------|--------|
| `audioDeviceReader` with `NumChannels=2` | `RecorderChannels=1:2` | audiostreamer records **1 channel by default**. You must set `RecorderChannels` explicitly for stereo/multichannel. |
| `audioDeviceWriter` returns underrun count per frame | `getUnderrunCount(as)` after loop | `play()` has no return value. Call `getUnderrunCount` when you need the total — it resets the counter each call. |
| `audioDeviceReader` returns overrun count per frame | No equivalent needed | audiostreamer buffers all recorded samples internally — recorder cannot overrun. |
| `Device='Default'` | Omit `Player`/`Recorder` | No "Default" string — omitting the property selects the system default for the current driver. |
| `[data, nOverrun] = deviceReader()` | `record(as)` then `data = read(as, N)` | Must call `record(as)` (or `start(as)` [R2026a+]) before the loop — otherwise `read` blocks forever waiting for samples. |
| Two separate objects for reader+writer | Single `audiostreamer(Mode="full-duplex")` | One object handles both directions. Use two separate objects if devices require different drivers or conflict when opened together. |

## Diagnostics

For debugging streaming issues, enable the diagnostic trace:
```matlab
as = audiostreamer(Mode="full-duplex", SampleRate=44100);
as.TraceEnabled = true;   % logs internal timing and buffer state
```

----

Copyright 2026 The MathWorks, Inc.

----