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

  1. Confirm the local scrcpy server jar version matches the protocol assumptions (codec meta + frame meta v2.4).
  2. Verify the connection handshake (dummy byte + 64-byte device name) is fully consumed before the first video chunk reaches the parser.
  3. On a desync, tear down the ScrcpyStreamSession and re-establish it rather than resuming mid-stream.
  4. 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

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


AI-assisted analysis of stablyai/orca@1136503c6a (2026-08-12). Data as JSON: /api/errors/e88cf6a1743b3020. Report an issue: GitHub.