Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

27 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

SwitchCast Dock

SwitchCast Dock is an experimental, low-latency USB receiver for a Raspberry Pi Zero 2 W. A Switch running SwitchCast 0.4.4 or SysDVR supplies its already encoded 720p H.264 gameplay stream and 48 kHz stereo PCM over USB. The Pi uses a tightly bounded FFmpeg software decoder—the path validated on the Zero 2 W—and places frames directly on HDMI through DRM/KMS. Audio uses an independent low-buffer ALSA branch. An experimental VideoCore decoder attempt is still available as an opt-in diagnostic.

This is a new prototype undergoing physical Pi Zero 2 W and television bring-up. It produces a complete, build-validated SD-card image, but it is not ready to replace a retail dock.

The receiver interoperates with SysDVR USB protocol 02/03 and with SwitchCast 0.4.4's native USB Dock transport. SwitchCast exposes mutually exclusive Cast and USB Dock choices in its own Settings screen; see Switch-side integration.

Why this path

The Chromecast path must packetize video, cross Wi-Fi, satisfy a Cast receiver, and obey receiver buffering. The USB dock path is intentionally shorter:

Switch capture/encode
        |
        | USB 2.0 bulk: SysDVR H.264 and PCM packets
        v
libusb receiver -> H.264 parser -> FFmpeg decoder -> DRM/KMS -> 1080p HDMI
                 \-> stereo PCM -> bounded ALSA queue -----------> HDMI audio
                                  \-> experimental VideoCore video (opt-in)

There is no HLS playlist, web receiver, network jitter buffer, desktop audio server, or audio transcoding.

Prototype wiring

  1. Power the Pi through its PWR IN micro-USB port.
  2. Put a micro-USB OTG adapter in the Pi's USB data port.
  3. Connect a normal USB-A-to-USB-C data cable from that adapter to the Switch.
  4. Connect the Pi's mini-HDMI output to the display.

The Pi is the USB host. This prototype arrangement does not charge the Switch. Do not combine USB-C power delivery with ad-hoc split cables. A self-contained dock that charges the console needs a correctly designed USB-C PD/power-path stage in front of this receiver.

Build the receiver on a normal Linux machine

Development packages for libusb 1.0, libpng, ALSA, GStreamer 1.0, gst-app, V4L2, and kmssink are required.

make
make test

Run it as root or install a udev rule that grants access to the SysDVR USB interface:

./switchcast-dock
./switchcast-dock --blank-switch-screen
./switchcast-dock --try-hardware-decoder

The screen-blank request is optional and only works with protocol 03. The receiver restores the console screen when the USB session closes.

Build the minimal Pi image

Clone Buildroot, then run:

git clone https://gitlab.com/buildroot.org/buildroot.git
git -C buildroot checkout cdeeb9b9c688b5bcdcc80bf89f2c046b0bd55bb8
./scripts/build-image.sh /path/to/buildroot

The result is output/images/sdcard.img inside this repository. Flash it to an SD card, wire the Pi as described above, and boot with HDMI attached. The image uses BusyBox init and starts the receiver directly; it has no desktop, login manager, Wi-Fi stack, or audio daemon.

New in 0.1.16

  • Replaced the raw diagnostic terminal with a branded 1080p Ready screen and Connected, Starting, Paused, Disconnected, and Check Connection states.
  • Kept the graphical status layer outside the gameplay pipeline so it adds no video queue or display latency.
  • Releases HDMI video after 1.5 seconds without gameplay frames, keeps USB connected, and restarts only from the next SPS/PPS/IDR access unit.
  • Added SwitchCast's optional protocol keepalive, endpoint-halt cleanup, and a longer handshake window for more reliable disconnect/reconnect recovery.
  • Added 48 kHz, 16-bit stereo PCM over USB and a two-buffer, downstream-leaky HDMI audio branch. Audio failure cannot stop or back-pressure video.
  • Preserved the validated four-core FFmpeg decoder and 1080p60 KMS display-plane scaling path.

First hardware diagnostics

Version 0.1.16 keeps a branded status display on the firmware HDMI framebuffer while it checks the USB host. Detailed diagnostics are written to /var/log/switchcast-dock.log. The unused VideoCore codec is not loaded or probed on the default software path, removing its startup wait and memory use. The VC4 video driver and GStreamer pipeline remain completely stopped during USB discovery and the SysDVR handshake. The receiver discards leading inter-frames and activates HDMI video only after an access unit containing SPS, PPS, and an IDR frame arrives:

branded HDMI Ready/status display
  -> SysDVR USB handshake
  -> decoder-start H.264 access unit (SPS + PPS + IDR)
  -> load VC4 firmware-KMS
  -> create decoder and kmssink pipeline
  -> hand HDMI from status to gameplay

This ordering makes the last visible transition unambiguous and prevents an empty sink from taking the display. The VideoCore codec remains built into the appliance kernel, and GStreamer receives a fresh, volatile plug-in registry on each boot.

The kernel also decompresses its XZ-packed modules directly. This is required by the minimal appliance because its userspace kmod intentionally does not carry a second XZ decompressor.

Version 0.1.16 uses firmware KMS (vc4-fkms-v3d) as a compatibility path. It still exposes DRM planes to kmssink, but the firmware retains responsibility for the HDMI timing that produced the working boot console. Full KMS had repeatedly caused the test television to lose sync during driver takeover. The HDMI link is fixed at 1920x1080p60, and the KMS display plane scales the native 1280x720 gameplay frame without a software resize or a second encode. This improves the output mode and scaling path; it does not create additional source detail because Horizon's continuous gameplay capture is fixed at 720p.

The compatibility pipeline uses V4L2 mmap capture and a multithreaded videoconvert fallback. The converter negotiates the hardware decoder's raw format against the formats exposed by the actual firmware-KMS plane instead of assuming that plane accepts I420. This can add one bounded conversion, but avoids requesting DMA-BUF or raw caps that the selected display plane cannot accept. Zero-copy DMA-BUF can be restored after the display path is reliable.

Version 0.1.16 follows the working SysDVR companion more closely. The companion initializes FFmpeg with SysDVR's fixed 1280x720 SPS/PPS and submits each received Annex-B packet directly to its selected H.264 decoder. It does not put the stream through a profile-changing transform. The dock requests SPS/PPS injection in the SysDVR handshake and submits each complete Annex-B access unit through h264parse to the selected decoder. The source caps carry SysDVR's fixed 1280x720/30 timing, while the parser remains free to preserve the exact profile and level found in the SPS. This path was checked against upstream SysDVR commit 804fd36, the reference snapshot used by SwitchCast.

SysDVR's fixed SPS identifies the bitstream as constrained-high, which is a restricted subset of High profile. The Pi's V4L2 decoder advertises the parent high capability but not the newer constrained-high enum. Version 0.1.16 patches GStreamer's V4L2 profile probe to advertise constrained-high whenever the driver supports its parent High profile. The parser can therefore preserve the real SPS and negotiate naturally with VideoCore instead of rewriting caps.

Version 0.1.16 starts avdec_h264 directly instead of first consuming a clean IDR on the VideoCore path that this Pi/driver combination has consistently rejected. This removes the most likely cause of the observed first-plug false start. --try-hardware-decoder restores the earlier hardware-first experiment for diagnostics. SysDVR's Game Recording capture produces one slice per frame, so slice threading cannot divide a frame among the Pi's cores. Frame threading permits the four Cortex-A53 cores to work concurrently. The minimal FFmpeg build contains only the H.264 decoder and parser; command-line tools, encoders, containers, networking, and unrelated filters are disabled. USB PCM is passed directly to ALSA without encoding.

After an established USB session ends, the receiver waits three seconds before another handshake. This matches SysDVR's timeout/reset behavior and prevents queued video bytes from being mistaken for a fresh SysDVR|03 greeting. An isolated malformed streaming transfer is discarded without restarting the session. Four consecutive malformed transfers trigger a clean reconnect.

The compressed app queue holds at most two access units and applies bounded backpressure instead of dropping encoded video. Discarding an H.264 reference frame makes later frames visibly corrupt until the next IDR; version 0.1.16 therefore permits freshness drops only in a one-frame raw-video queue after the decoder. During startup, buffer probes count frames after the parser, at decoder output, converter, and display. Once video is running, the five-second status line reports the rate at all three important boundaries, compressed queue depth, bitrate, and USB timeline drift. If no frame reaches the display within ten seconds, the receiver prints those counts and the negotiated caps before restarting cleanly.

The receiver attempts to advertise SwitchCastDock when the selected display driver exposes HDMI-CEC. HDMI-CEC names are limited to 14 visible characters, so the space is omitted. Firmware KMS may not expose a CEC adapter; this compatibility build reports that limitation without preventing video.

  • waiting for SysDVR 18d1:4ee0: the Pi host is running but the expected USB identity is not present; check the OTG adapter, data cable, and SysDVR mode;
  • Other device(s): USB is enumerating something, but it is not the SysDVR identity;
  • bulk interface is missing: the Switch enumerated with the expected identity, but SysDVR did not publish its streaming interface;
  • USB connected: enumeration, interface discovery, and the protocol handshake passed;
  • decoder remains stopped until gameplay video arrives: USB is ready and the firmware diagnostic console still owns HDMI;
  • waiting for SPS/PPS/IDR decoder start: gameplay packets are arriving, but the receiver is deliberately waiting for a clean H.264 entry point;
  • Gameplay video packet received: valid H.264 arrived and display activation is beginning;
  • handing HDMI from diagnostics to video: VC4, DRM, and the GStreamer pipeline all started;
  • Pipeline caps at failure: compact per-element caps show the exact boundary that rejected a stream if runtime negotiation still fails;
  • Switching automatically to the FFmpeg software decoder: an opt-in VideoCore attempt was refused, but USB remains connected and the receiver will retry on the next clean decoder-start access unit;
  • Pipeline flow after: input/decoder/converter/display counters show how far frames reached when a pipeline starts but produces no HDMI frame;
  • USB ... parser ... decoder ... display: live rates identify whether delay is accumulating before decode, inside decode, or at HDMI;
  • Pipeline creation failed or Video pipeline error: USB worked and the remaining problem is hardware decode or display.

The commit above is the revision used for the first full validation. The integration is based on Buildroot's official raspberrypizero2w_64_defconfig, with an internal musl toolchain so it can be built from either an ARM64 or x86-64 Linux host. Buildroot itself expects a Linux build host; on macOS, run the command inside a Linux VM such as Lima.

Status

  • USB protocol parser and deterministic unit tests: implemented.
  • USB discovery, endpoint selection, handshake, reconnect: implemented.
  • Experimental GStreamer VideoCore decode path: implemented and retained as an opt-in diagnostic.
  • Minimal FFmpeg H.264 path: implemented and selected by default after successful physical latency/framerate validation.
  • Reproducible Buildroot external tree: implemented; a complete ARM64 musl cross-build and 224 MiB SD image generation passed.
  • Target-root smoke test: receiver starts and exits cleanly when no USB device is attached; the KMS sink, plugin scanner, codec/display kernel modules, init service, and boot partitions are present.
  • SwitchCast 0.4.4 native USB Dock mode, low-delay H.264 SPS signaling, and Settings selector: implemented and build-validated.
  • Native V4L2-to-DRM replacement: planned after first hardware trace.
  • Bare-metal receiver: researched; not the first implementation path.

See USB protocol, architecture and latency, bare-metal feasibility, and Switch-side integration. The exact first-test wiring is in prototype hardware.

Attribution and license

SysDVR by exelix and its contributors is the principal foundation of this project. It provides the Switch capture system and the USB streaming protocol that make SwitchCast Dock possible:

https://github.com/exelix11/SysDVR

SwitchCast Dock is licensed under GNU GPL version 2 only, matching SysDVR. See LICENSE and NOTICE. No affiliation with Nintendo or Raspberry Pi Ltd is claimed.

About

Low-latency Raspberry Pi Zero 2 W USB-to-HDMI receiver for SwitchCast and SysDVR.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages