Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Reference for MATLAB audiostreamer (Audio Toolbox R2025a+). Without this skill, agents consistently default to legacy audioDeviceWriter/audioDeviceReader or base MATLAB sound(), producing less capable code. Use when writing code for audio playback, recording, full-duplex device I/O, real-time audio measurements, or audio I/O processing with callbacks. Also use when debugging audiostreamer errors, dropouts, or latency issues, or migrating from audioDeviceReader, audioDeviceWriter, audioPlayerReco
.claude/skills/matlab-matlab-play-record-audio/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 57% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 140% | 0% |
| case-07 | ✗→✓ | ▲ Improved | 82% | 0% |
| case-08 | ✗→✓ | ▲ Improved | 244% | 0% |
| case-09 | ✗→✓ | ▲ Improved | 119% | 0% |
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.
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.audiostreamer does not yet support codegen; use legacy APIs if targeting codegenaudiostreamersound/soundsc, audioplayer/audiorecorder, or audioDeviceWriter (DSP System Toolbox) if the user lacks Audio Toolboxaudioread/audiowrite, not this skilldaq objectmididevice/midicontrols, not audiostreamerUse 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.
matlabas = 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])
| 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.
| 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.NumPlayerSamplesRecorderFcn: event.Type = "Recorder", event.NumRecorderSamplesPlayerCompletedFcn: event.Type = "PlayerCompleted", event.StreamTimeRecorderCompletedFcn: event.Type = "RecorderCompleted", event.StreamTimePlayerUnderrunFcn: event.Type = "PlayerUnderrun", event.SamplesUnderrun| 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 |
| 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. |
| 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. |
| 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.
| 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. |
| 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. |
matlabaudiostreamer.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.
setup() MethodThe 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.
matlabas = 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);
matlabas = 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);
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.
matlabas = audiostreamer(Mode="player", SampleRate=48000); write(as, signal); % queue without starting start(as); % begin playback waitfor(as); % wait for completion release(as);
matlabas = 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);
matlabas = 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);
The most direct replacement for legacy audioDeviceReader/audioDeviceWriter loops. Call start(as) before the loop so that read has samples available.
matlabas = 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.
matlabas = 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);
matlabas = 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:
matlabfunction 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
matlabas = 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);
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.
matlabwaitfor(as); % ensure all queued audio finishes playing release(as);
In apps, wrap in try-catch and nil the reference:
matlabtry 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.
| 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 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 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.
| 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. |
For debugging streaming issues, enable the diagnostic trace:
matlabas = audiostreamer(Mode="full-duplex", SampleRate=44100); as.TraceEnabled = true; % logs internal timing and buffer state
Copyright 2026 The MathWorks, Inc.
Other measured skills in the registry, with their headline benchmark lift.