Skip to content

Feat/rtsp server and UI - #68

Closed
Gabrick75 wants to merge 8 commits into
DavidVentura:masterfrom
Gabrick75:feat/rtsp-server-and-ui
Closed

Feat/rtsp server and UI#68
Gabrick75 wants to merge 8 commits into
DavidVentura:masterfrom
Gabrick75:feat/rtsp-server-and-ui

Conversation

@Gabrick75

Copy link
Copy Markdown

Pull Request: RTSP Server with H.264/JPEG, Modern HTTP UI, Docker Support, and Full Documentation

Metadata

Field Value
Target Branch DavidVentura/cam-reverse:master
Source Branch Gabrick75/cam-reverse-rtsp:feat/rtsp-server-and-ui
Type Feature
Scope Streaming, UI, Infrastructure, Documentation
Breaking Changes None

Executive Summary

This pull request introduces native RTSP streaming capability to cam-reverse, enabling direct camera integration with Network Video Recorders (NVRs), VLC, Blue Iris, Frigate, Home Assistant, and any RTSP/RTP-compliant client. The implementation includes a full RTSP/RTP server with H.264 (via GStreamer transcoding) and JPEG/RTP (RFC 2435) modes, TCP and UDP transport, RTCP Sender Reports, and SDP negotiation. Additionally, the HTTP dashboard has been modernized with a responsive dark/light theme, real-time metrics, and camera search. Docker support and comprehensive documentation are included.


Technical Changes

1. RTSP Server (rtsp_server.ts)

Implementation Details:

  • Protocol Compliance: RFC 2326 (RTSP 1.0), RFC 3550 (RTP/RTCP), RFC 2435 (JPEG/RTP), RFC 6184 (H.264/RTP)
  • Transport Modes:
    • TCP: Interleaved binary framing ($ + channel + length + payload) per RFC 2326 §10.12
    • UDP: Dynamic RTP/RTCP port binding with client_port/server_port negotiation
  • Session Management: Per-client state (seqNum, timestamp, SSRC, packet/octet counters, RTCP interval timer)
  • Methods Implemented: OPTIONS, DESCRIBE, SETUP, PLAY, TEARDOWN, GET_PARAMETER/SET_PARAMETER (keepalive)
  • RTCP Sender Reports: Periodic (5s interval) per RFC 3550 §6.4.1 with NTP/RTP timestamp synchronization

SDP Generation:

  • JPEG mode (PT=26): a=fmtp:26 quantization=255; width=640; height=480
  • H.264 mode (PT=96): a=fmtp:96 packetization-mode=1; profile-level-id=42C01E (Constrained Baseline)

JPEG/RTP Packetization (RFC 2435 §3):

  • Fragmentation across MTU (1400 bytes payload)
  • JPEG header extraction (SOF0 for dimensions, DQT for quantization tables)
  • Scan data isolation (post-SOS marker)
  • RTP header + JPEG-specific header (8 bytes) + optional Q-table header

2. GStreamer Transcoder (transcoder.ts)

Pipeline Architecture:

fdsrc (stdin) → jpegdec → videoconvert → openh264enc
  → video/x-h264,stream-format=byte-stream,profile=constrained-baseline
  → h264parse → rtph264pay (pt=96, config-interval=-1)
  → udpsink (127.0.0.1:dynamic_port)

Encoder Parameters:

  • complexity=low (real-time encoding)
  • bitrate=300000 (300 kbps target)
  • gop-size=15 (keyframe interval)
  • usage-type=camera (tuned for live source)

Operational Features:

  • SPS/PPS extraction from RTP NAL units (types 7/8, including FU-A fragmentation)
  • Stdin backpressure handling with frame-drop logging
  • Automatic restart on crash with event emission
  • Clean shutdown (SIGTERM + stdin EOF)

Availability Detection:

  • Checks gst-launch-1.0 --version
  • Verifies openh264enc plugin via test pipeline
  • Graceful fallback to JPEG/RTP if unavailable

3. HTTP Dashboard (http_server.ts, asd.html)

UI Enhancements:

  • Theme System: CSS custom properties with data-theme attribute, localStorage persistence, system preference detection
  • Responsive Grid: grid-template-columns: repeat(auto-fill, minmax(340px, 1fr))
  • Real-time Metrics: FPS counter, signal quality indicator, connection status per camera
  • Search/Filter: Client-side filtering by camera name/ID
  • Per-Camera Controls: Rotate (90° increments), Mirror toggle, Audio enable/disable

Technical Stack:

  • Vanilla ES5-compatible JavaScript (no build step)
  • Server-Sent Events (SSE) for audio streaming
  • multipart/x-mixed-replace for MJPEG
  • EXIF orientation injection for rotation/mirror (no pixel manipulation)

4. Docker Support (Dockerfile)

Multi-Stage Build:

# Build stage
FROM node:22-slim AS build
RUN npm ci && npm run build

# Runtime stage
FROM node:22-slim
RUN apt-get update && apt-get install -y --no-install-recommends \
    gstreamer1.0-tools \
    gstreamer1.0-plugins-base \
    gstreamer1.0-plugins-good \
    gstreamer1.0-plugins-bad \
    openh264 \
  && rm -rf /var/lib/apt/lists/*
COPY --from=build /app/dist/bin.cjs ./dist/bin.cjs
USER cam
EXPOSE 8554/tcp
ENTRYPOINT ["node", "dist/bin.cjs", "rtsp_server"]
CMD ["--discovery_ip", "192.168.1.255"]

Key Decisions:

  • openh264 package required for openh264enc plugin (licensing separation in Debian)
  • Non-root user (cam) for security
  • TCP-only expose (UDP ports are dynamic; document host networking requirement)

5. Documentation (docs/)

File Content
architecture.md Project structure, data flow, source file index
rtsp.md RTSP/RTP streaming, H.264/JPEG modes, transport, compatibility matrix
http_server.md MJPEG streaming, web UI routes, configuration
protocol.md iLnkP2P/PPPP reverse-engineered protocol specification
gstreamer.md JPEG→H.264 pipeline, encoder tuning, troubleshooting
reversing.md Ghidra/Frida/Wireshark methodology, dissector usage
guide-initial-setup.md Building, pairing, running, configuration reference

Dependency Changes

Removed

  • node-media-server@^4.2.4Unused dependency (present in package.json but never imported)

Added (Runtime Only)

  • gstreamer1.0-tools, gstreamer1.0-plugins-{base,good,bad}, openh264 — Via Dockerfile / host installation
  • No new npm dependencies

Verification

Automated Checks

npm run build    # esbuild → dist/bin.cjs (937 KB) ✓
npm test         # Mocha: 23 passing (protocol, integration) ✓
npm run tsc      # TypeScript: zero errors ✓
npx prettier --check .  # Formatting: clean ✓

Manual Validation

Scenario Command Expected
RTSP (JPEG) node dist/bin.cjs rtsp_server --discovery_ip 192.168.1.255 VLC/Blue Iris connects to rtsp://<ip>:8554/camera
RTSP (H.264) Same (requires GStreamer + openh264) NVR receives H.264 Constrained Baseline
HTTP Dashboard node dist/bin.cjs http_server --discovery_ip 192.168.1.255 http://<ip>:5000 renders dark/light theme, search, FPS
Docker docker build -t cam-reverse . && docker run --net=host cam-reverse RTSP server starts in container

Known Limitations

Multi-Camera RTSP Endpoint

Current Behavior: All discovered cameras stream to a single RTSP endpoint: rtsp://<host>:8554/camera

Impact: NVRs cannot distinguish between cameras; only the first/active camera's stream is delivered.

Workaround: Run separate rtsp_server instances per camera on distinct ports (e.g., 8554, 8555, 8556) with filtered discovery IPs.

Resolution: Planned follow-up to implement path-based routing (/camera/<devId>) with session-to-camera mapping.


Backward Compatibility

  • No breaking changes to existing HTTP server API or CLI commands
  • New rtsp_server command is additive
  • Configuration schema unchanged (extends existing cameras object)
  • Core protocol modules (session.ts, handlers.ts, impl.ts, discovery.ts) unmodified

References

  • Upstream Repository: https://github.com/DavidVentura/cam-reverse
  • Camera Hardware: TXW817-based X5/A9/A7 (iLnkP2P/PPPP protocol)
  • Mobile App: YsxLite (com.ysxlite.cam)
  • Protocol Reference: docs/protocol.md, dissector.lua

Gabrick75 and others added 8 commits April 25, 2026 13:39
…-reverse fork

- implement rtsp_server command
- stream MJPEG frames over RTSP (RTP/JPEG)
- TCP interleaved transport (RFC 2326)
- compatible with VLC, NVRs, Home Assistant
- no external dependencies (ffmpeg/mediamtx not required)
…tors, camera search

Redesign the HTTP server UI with a modern dark theme (default) / light theme toggle,
responsive grid dashboard with camera search/filter, FPS and signal quality indicators
on the live stream view, and updated README with mobile/PC screenshots.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants