Commit Graph

4 Commits

Author SHA1 Message Date
fegger 3f8e92c6af fix(codec): bound keyframe bursts with VBV and microsecond time_base
The first real sender/receiver run failed permanently: the receiver
reported 'non-existing PPS 0' for every frame. Without a VBV, a
2256x1504 IDR keyframe bursts hundreds of kilobytes of back-to-back
FU-A packets, overflowing the ~208KB default UDP receive buffer; the
resulting sequence gap made the depacketizer drop whole keyframes
including their in-band SPS/PPS, so the decoder never initialized and
never recovered, because every keyframe burst overflowed again.

- Encoder: add rc_max_rate = bitrate and rc_buffer_size =
  bitrate*2/fps, capping any single frame to about two frame periods
  of bytes (~40KB at the 4Mbps default).
- Encoder: switch the time_base to microseconds. It was derived from
  the configured frame rate, which quantized capture-rate timestamps
  and duplicated pts; RTP timestamps are now lossless.
- Transport: request a 4MB SO_RCVBUF on the receive socket
  (best-effort; the kernel clamps to net.core.rmem_max).

meson test 4/4, valgrind clean (loopback + codec).
2026-09-07 11:06:03 +02:00
fegger 10870bc6c9 feat(app): implement Phase 5 local UDP sender->receiver loopback
Wire the first end-to-end pipeline: capture -> encode -> packetize ->
UDP -> depacketize -> decode -> render.

- UdpRtpTransport: raw POSIX UDP sockets (IPv4 via getaddrinfo), a
  receive jthread woken by socket close on stop; port 0 skips binding
  so the sender uses an OS-assigned source port. ASIO stays deferred
  to the signaling phase per ARCHITECTURE.md.
- SdlRenderer: SDL3 window/renderer with RGBA texture upload; the
  texture is recreated on resolution change. RendererFactory now
  returns RendererResult so SDL init failures carry a message,
  mirroring the codec/capture error patterns.
- screencast binary: parse_cli plus SenderPipeline/ReceiverPipeline
  per the app scaffolds; the sender creates its encoder once capture
  reports real dimensions, the receiver keeps a bounded 3-frame queue
  to hold latency down and renders on its own thread until the window
  closes. cli argv signature fixed to 'const char* const*' so main's
  argv converts implicitly.
- Encoder: drop AV_CODEC_FLAG_GLOBAL_HEADER so libx264 repeats SPS/PPS
  in-band at each keyframe -- the receiver decodes from the bitstream
  alone, which also makes mid-stream joins and later PLI recovery
  work without out-of-band parameter negotiation. The round-trip test
  now exercises exactly that path.
- tests: new udp-loopback integration test pushes synthetic frames
  through a real localhost socket and decodes 10/10 frames with the
  right dimensions; valgrind clean (loopback + codec). meson test 4/4.

Manual validation on the desktop (receiver window shows the captured
desktop) is documented in docs/RUNBOOK.md.
2026-09-07 11:02:40 +02:00
fegger ce52f64e52 feat(capture): implement Phase 3 PipeWire/portal desktop capture
Implement the xdg-desktop-portal ScreenCast backend via libportal: a
blocking portal handshake (interactive source picker), a PipeWire stream
on the portal's node enumerating BGRx/BGRA/RGBx/RGBA, and a latest-frame
slot handing frames to next_frame(). stop() is thread-safe; teardown
follows the order PipeWire requires. All proxy operations run under the
thread-loop lock to satisfy the protocol extension context checks
('impl_ext_end_proxy called from wrong context' otherwise).

The encoder now accepts padded strides for packed RGB inputs (real
PipeWire row pitches) and maps the new PixelFormat::Bgrx to
AV_PIX_FMT_BGRA.

Add tools/capture_smoke: a manual smoke tool (interactive, not in
meson test) that captures N frames, encodes them, and writes a
self-contained Annex-B elementary stream with prepended SPS/PPS.

Validated manually on Wayland/Hyprland: 2256x1504 H.264 elementary
stream, ffprobe clean. Phase 3 marked complete in docs/PHASES.md.
2026-09-07 10:37:14 +02:00
fegger c6c062250e fix(codec): fix decoder packet leak and extradata over-read
Allocate packet payloads with av_new_packet so they are freed through the
owning AVBufferRef instead of leaking on every decode. Pad extradata with
AV_INPUT_BUFFER_PADDING_SIZE for FFmpeg's bitstream parsers. Reject
oversized frames before the int cast, handle unexpected EOF in the
EAGAIN-retry loops, and add the missing <limits> include. Document the
valgrind codec check in docs/RUNBOOK.md.

Validated with valgrind: 0 bytes definitely lost, 0 invalid reads.
2026-09-07 10:17:59 +02:00