stablyai/orca · critical
scrcpy frame size ${size} exceeds ${MAX_FRAME_BYTES}; stream
Error message
scrcpy frame size ${size} exceeds ${MAX_FRAME_BYTES}; stream desynced What it means
Thrown by parseScrcpyVideoFrames when a frame's declared size field exceeds MAX_FRAME_BYTES (16 MiB). scrcpy frames at the configured max_size are far below this ceiling, so an oversized value indicates the byte stream is desynced — the parser is reading a size field at the wrong offset. Failing fast prevents unbounded buffering toward OOM.
Source
Thrown at src/main/emulator/android/scrcpy-video-frame-parser.ts:63
keyFrame: boolean
pts: bigint
data: Buffer
}
export type ScrcpyFrameParseResult = { frames: ScrcpyVideoFrame[]; pending: Buffer }
// Extracts complete frames from `pending + chunk`, returning the leftover bytes
// of any partially-received frame so the caller can prepend them to the next chunk.
export function parseScrcpyVideoFrames(pending: Buffer, chunk: Buffer): ScrcpyFrameParseResult {
const buffer = pending.length > 0 ? Buffer.concat([pending, chunk]) : chunk
const frames: ScrcpyVideoFrame[] = []
let offset = 0
while (buffer.length - offset >= FRAME_HEADER_SIZE) {
const meta = buffer.readBigUInt64BE(offset)
const size = buffer.readUInt32BE(offset + 8)
if (size > MAX_FRAME_BYTES) {
throw new Error(`scrcpy frame size ${size} exceeds ${MAX_FRAME_BYTES}; stream desynced`)
}
const dataStart = offset + FRAME_HEADER_SIZE
if (buffer.length - dataStart < size) {
break
}
frames.push({
config: (meta & CONFIG_FLAG) !== 0n,
keyFrame: (meta & KEY_FRAME_FLAG) !== 0n,
pts: meta & PTS_MASK,
data: Buffer.from(buffer.subarray(dataStart, dataStart + size))
})
offset = dataStart + size
}
return { frames, pending: offset > 0 ? Buffer.from(buffer.subarray(offset)) : buffer }
}
View on GitHub (pinned to 1136503c6a)
Solutions
- Confirm the local scrcpy server jar version matches the protocol assumptions (codec meta + frame meta v2.4).
- Verify the connection handshake (dummy byte + 64-byte device name) is fully consumed before the first video chunk reaches the parser.
- On a desync, tear down the ScrcpyStreamSession and re-establish it rather than resuming mid-stream.
- Ensure no control-channel bytes are mixed into the video socket.
Example fix
// before: resuming a parser after a socket error corrupted offsets
const { frames, pending } = parseScrcpyVideoFrames(stalePending, chunk)
// after: reset pending on reconnect and re-run the handshake
if (reconnected) { pending = Buffer.alloc(0); await consumeHandshake(videoSocket) }
const { frames, pending: next } = parseScrcpyVideoFrames(pending, chunk) Defensive patterns
Strategy: try-catch
Validate before calling
// Reset parser state whenever the socket (re)connects so offsets stay aligned.
function freshParserState(): Buffer { return Buffer.alloc(0) } Type guard
function isStreamDesync(e: unknown): e is Error {
return e instanceof Error && /stream desynced/i.test(e.message)
} Try / catch
let pending = Buffer.alloc(0)
for await (const chunk of videoSocket) {
try {
const res = parseScrcpyVideoFrames(pending, chunk)
pending = res.pending
res.frames.forEach(cb.onFrame)
} catch (e) {
if (isStreamDesync(e)) { session.close(); throw e } // re-establish, do not resume
throw e
}
} Prevention
- Fully consume the handshake (dummy byte + 64-byte name + codec meta) before parsing frames.
- Reset pending bytes on any socket reconnect; never resume a desynced parser.
- Keep the local scrcpy server jar version matched to the protocol assumptions (v2.4).
When it happens
Trigger: parseScrcpyVideoFrames(pending, chunk) reads the 12-byte frame header (8-byte meta + 4-byte size) at an offset that is not actually a frame boundary. This happens when earlier bytes were dropped, duplicated, or misinterpreted — e.g. the codec meta header was not stripped, a control-protocol byte leaked into the video stream, or the scrcpy server version uses a different frame-meta layout.
Common situations: A scrcpy server version mismatch (the pinned server jar expects send_codec_meta + send_frame_meta v2.4; a different version changes the header); the dummy byte / 64-byte device name handshake was not consumed before video parsing; a socket reconnection that lost partial frame bytes; stream corruption from an adb tunnel issue.
Related errors
- emulator_helper_failed
- adb did not return a local scrcpy port
- Unexpected helper handshake: ${JSON.stringify(capabilities)}
- invalid relay hello JSON
- expected plaintext relay hello
AI-assisted analysis of stablyai/orca@1136503c6a (2026-08-12).
Data as JSON: /api/errors/e88cf6a1743b3020.
Report an issue: GitHub.