microsoft/semantic-kernel · error · MediaStreamError
Failed to receive audio frame
Error message
Failed to receive audio frame
What it means
MediaStreamError raised by the realtime sample's audio track recv(): the await on the internal asyncio queue (self._queue.get()) is wrapped in try/except, and any Exception during dequeue is logged then re-raised as MediaStreamError('Failed to receive audio frame'). It is a sample-level wrapper around aiortc's MediaStreamTrack meant to surface audio-capture failures to the WebRTC peer consistently.
Source
Thrown at python/samples/concepts/realtime/utils.py:104
"channels": channels,
"frame_duration": frame_duration,
"dtype": dtype,
"frame_size": int(sample_rate * frame_duration / 1000),
})
MediaStreamTrack.__init__(self)
async def recv(self) -> Frame:
"""Receive the next frame of audio data."""
if not self._recording_task:
self._recording_task = asyncio.create_task(self.start_recording())
try:
frame = await self._queue.get()
self._queue.task_done()
return frame
except Exception as e:
logger.error(f"Error receiving audio frame: {e!s}")
raise MediaStreamError("Failed to receive audio frame")
def _sounddevice_callback(self, indata: np.ndarray, frames: int, time: Any, status: Any) -> None:
if status:
logger.warning(f"Audio input status: {status}")
if self._loop and self._loop.is_running():
asyncio.run_coroutine_threadsafe(self._queue.put(self._create_frame(indata)), self._loop)
def _create_frame(self, indata: np.ndarray) -> Frame:
audio_data = indata.copy()
if audio_data.dtype != self.dtype:
audio_data = (
(audio_data * 32767).astype(self.dtype) if self.dtype == np.int16 else audio_data.astype(self.dtype)
)
frame = AudioFrame(
format="s16",
layout="mono",
samples=len(audio_data),
)View on GitHub (pinned to c028a0c7dc)
Solutions
- Confirm the microphone device id passed to the recorder exists and is permitted (check OS audio permissions).
- Ensure the asyncio event loop is running for the lifetime of the track so _sounddevice_callback can enqueue frames.
- Catch MediaStreamError at the consumer and end the WebRTC track cleanly rather than crashing.
- Verify sounddevice can open the stream at the configured sample_rate/channels/dtype before starting.
Example fix
# before
try:
frame = await self._queue.get()
self._queue.task_done()
return frame
except Exception as e:
logger.error(f"Error receiving audio frame: {e!s}")
raise MediaStreamError("Failed to receive audio frame")
# after - distinguish cancellation from real errors, preserve cause
try:
frame = await self._queue.get()
self._queue.task_done()
return frame
except asyncio.CancelledError:
raise
except Exception as e:
logger.error(f"Error receiving audio frame: {e!s}")
raise MediaStreamError("Failed to receive audio frame") from e Defensive patterns
Strategy: try-catch
Validate before calling
# Before starting the track, confirm the device can be opened.
import sounddevice as sd
def device_available(device, sample_rate, channels, dtype):
try:
sd.check_input_settings(device=device, samplerate=sample_rate,
channels=channels, dtype=dtype)
return True
except Exception:
return False Type guard
def track_ready(track) -> bool:
import asyncio
return (
getattr(track, "_loop", None) is not None
and track._loop.is_running()
and getattr(track, "_recording_task", None) is not None
and not track._recording_task.done()
) Try / catch
try:
frame = await track.recv()
except MediaStreamError:
# end the WebRTC track cleanly; do not crash the peer
await track.stop() Prevention
- Verify the mic device id and OS audio permissions before starting the track.
- Keep the asyncio loop running for the track's lifetime so frames keep flowing.
- Catch MediaStreamError at the consumer and stop the track instead of crashing.
- Preserve the original exception (raise ... from e) for diagnosability.
When it happens
Trigger: The queue.get() / task_done() call raises - e.g. the recording task was cancelled, the event loop is closed, the queue is in a bad state, or the underlying sounddevice callback stopped feeding frames (device disconnected, permission revoked, loop not running).
Common situations: Audio input device removed/disconnected mid-session; microphone permission denied by the OS; the asyncio loop (self._loop) stopped so run_coroutine_threadsafe callbacks no longer enqueue frames; or the track's recording task was cancelled during teardown.
Related errors
- Audio is required for InputAudioBufferAppendEvent
- Invalid operator
- No chart generated
- Invalid filename: would write outside the expected directory
- No chart generated
AI-assisted analysis of microsoft/semantic-kernel@c028a0c7dc (2026-08-13).
Data as JSON: /api/errors/147e349063d983c1.
Report an issue: GitHub.