CodeVetter · Repo Unpack · Data flow
Genymobile/scrcpy
Outbound (device->host): capture (screen/camera/mic) -> MediaCodec encoder -> Streamer writes a 4-byte codec id then 12-byte frame/session headers + raw packets onto the video/audio sockets -> client demuxer threads parse headers into AVPackets -> packet_source fans out to sinks (decoder -> screen/v4l2, recorder). Inbound (host->device): SDL input events -> processors build sc_control_msg -> controller queue -> serialized onto the control socket -> ControlMessageReader -> Controller.handleEvent -> Device/InputManager injection or UHID writes. The control socket is also used device->client for DeviceMessages (clipboard, ack, UHID output).
- Wire format: each stream starts with a 4-byte codec id (ASCII like 'h264'); then video gets a 12-byte session header (MSB set, width/height) and each media packet is a 12-byte header (64-bit PTS+flags: bit62 config, bit61 keyframe; 32-bit size) followed by raw packet data.
- Server video pipeline: SurfaceCapture (ScreenCapture / NewDisplayCapture / CameraCapture) feeds a Surface into SurfaceEncoder wrapping MediaCodec; encoded buffers go through Streamer.writePacket() to the video FileDescriptor.
- Server audio pipeline: AudioCapture (AudioDirectCapture for mic-family sources, AudioPlaybackCapture otherwise) is encoded by AudioEncoder or sent raw by AudioRawRecorder through a Streamer onto the audio fd.
- Client demuxer run_demuxer() reads the codec id (0 = stream disabled by device, 1 = config error), opens an AVCodecContext (LOW_DELAY, YUV420P for video, stereo 48kHz for audio), then loops pushing session updates and merged packets to all packet_source sinks.
- H.264/H.265 config packets are merged into the following media packet by sc_packet_merger before dispatch; other codecs pass through.
- Sink fan-out is wired in scrcpy(): video_demuxer.packet_source -> video_decoder.packet_sink -> (optional video_regulator) -> screen.frame_sink and/or v4l2_sink; both demuxers also feed recorder.packet_sinks when --record is set.
- Control uplink: sc_controller_push_msg() enqueues sc_control_msg (limit SC_CONTROL_MSG_QUEUE_LIMIT=60; only UHID_CREATE/UHID_DESTROY are non-droppable), run_controller serializes with sc_control_msg_serialize() into a static buffer and net_send_all()s to the control socket; RESIZE_DISPLAY is coalesced via a separate field.
- Control downlink: ControlMessageReader.read() parses the same type byte + big-endian fields into ControlMessage objects, then Controller.handleEvent() dispatches to injectKeycode/injectTouch/injectScroll/clipboard/UHID/camera actions.
- Device->client messages (clipboard text, clipboard ack, UHID output) are serialized by DeviceMessageWriter/DeviceMessageSender onto the control socket and deserialized in the client receiver thread, which schedules SDL clipboard writes on the main thread.
- Handshake metadata: the server sends a fixed 64-byte device name on the first connected socket (sendDeviceMeta), read by client device_read_info() into sc_server_info for use as the window title.
- Options flow: CLI -> scrcpy_options -> sc_server_params -> execute_server() renders 'key=value' args (with VALIDATE_STRING against shell metacharacters) -> Server Options.parse() switch reconstructs typed Options.
- Recorder muxing: packet sinks reference AVPackets into a queue; the recorder thread writes them with av_interleaved_write_frame() after rescaling from SCRCPY_TIME_BASE (1/1e6 us) to each stream's time_base, fixing non-monotonic PTS.
- Session identity: client generates a 31-bit random scid per run; the device socket name 'scrcpy_%08x' lets multiple scrcpy instances coexist on one device.