Note on origin: this project was developed with AI assistance (Anthropic Claude, via the Claude Code CLI). All design decisions, protocol choices, licensing, and final code review are by the human author; the AI did the bulk of the typing. Please treat it as you would any other contributor's code: read it, test it, file issues if it bites you.
VLC plugin that decodes the Ultimate 64 / Commodore 64 Ultimate video and audio stream so you can watch your C64 picture and hear it natively in VLC, with no external viewer.
The Ultimate-64 (FPGA-based C64 reimplementation, gideoncrafts) can broadcast its VIC video output as raw UDP datagrams. This plugin parses those datagrams in-kernel… well, in-VLC, and feeds RGBA frames straight into the standard VLC video pipeline.
Status: video only (50 Hz PAL or 60 Hz NTSC). Audio (port 11001) is on the roadmap.
After installing the plugin (see below) and pointing the U64's video stream at your machine on UDP port 11000:
vlc u64://@:11000
Or, equivalently, drop a one-line .m3u:
u64://@:11000
URL forms accepted:
| URL | Effect |
|---|---|
u64:// |
bind *:11000 unicast |
u64://@:11000 |
bind *:11000 unicast |
u64://@192.168.1.50:11000 |
bind a specific local interface |
u64://:11500 |
bind *:11500 |
u64://239.0.1.64@:11000 |
join multicast 239.0.1.64 on * (audio auto: 239.0.1.65) |
u64://239.0.1.64@192.168.1.50:11000 |
multicast on a specific local interface |
The U64's default streaming setup uses a multicast pair, e.g. 239.0.1.64
for video and 239.0.1.65 for audio. The plugin auto-derives the audio
multicast group from the video group by incrementing the last IPv4 octet,
so you don't normally need --u64stream-audio-group.
--u64stream-port=N Override video port from the URL (default 11000)
--u64stream-audio-port=N Audio UDP port (default 0 = video-port + 1)
--u64stream-audio-group=IP Audio multicast group (default empty:
auto-derive from video group, last octet +1)
--u64stream-no-audio Disable audio (video-only)
--u64stream-no-video Disable video (audio-only)
--u64stream-mode=N -1 = auto-detect (default), 0 = PAL, 1 = NTSC
--u64stream-source=IP Only accept packets from this source IP
(useful when several U64s share a LAN)
--u64stream-on-loss=N On packet loss: 0 = keep last frame's pixels
(default, smoother), 1 = clear missing rows
to black on each new frame
--u64stream-control-host=H[:P] Telnet (TCP/23 by default) into the C64U at H
and send the F5 menu keystrokes that toggle the
video+audio stream on. Fragile — depends on
the U64 firmware menu layout. Leave empty if
you start the stream manually.
--u64stream-sar-num=N Pixel aspect ratio numerator (0 = mode default)
--u64stream-sar-den=N Pixel aspect ratio denominator (0 = mode default)
Selected URL query parameters are also recognised, useful for embedding in
.m3u playlists:
?source=IP same as --u64stream-source=IP
Aspect-ratio defaults make the full frame display at 4:3 (CRT-era look):
- PAL: 17:18 (≈0.944) — 384×272 → 4:3 exact
- NTSC: 5:6 (≈0.833) — 384×240 → 4:3 exact
Override with --u64stream-sar-num / --u64stream-sar-den if you prefer
something else. Common alternatives:
117:125(PAL) /3:4(NTSC) — strict VIC-II pixel aspect (what VICE uses)1:1— raw square pixels (slightly wide-ish on a 4:3 monitor)
Requires Meson, Ninja, a C11 compiler, and the VLC plugin SDK headers
(usually shipped as vlc-plugin.pc for pkg-config).
sudo apt install meson ninja-build build-essential libvlccore-dev libvlc-dev
meson setup build
meson compile -C build# from the MINGW64 shell
pacman -S --needed mingw-w64-x86_64-toolchain \
mingw-w64-x86_64-meson \
mingw-w64-x86_64-ninja \
mingw-w64-x86_64-pkgconf \
mingw-w64-x86_64-vlc
meson setup build --buildtype=release
meson compile -C build
# Artefact: build/libu64stream_plugin.dllDrop the .dll into %APPDATA%\vlc\plugins\ (or VLC's plugins\access\
directory) for it to be picked up.
brew install meson ninja pkg-config
brew install --cask vlc # provides the SDK headers in VLC.app
# Point pkg-config at VLC.app's headers (the cask doesn't ship a .pc file).
# See .github/workflows/build.yml for a copy-paste version.
meson setup build --buildtype=release
meson compile -C build
# Artefact: build/libu64stream_plugin.dylibEach push runs the full Linux + Windows + macOS matrix and uploads the shared modules as workflow artefacts. See .github/workflows/build.yml.
Per-user (no root required):
meson compile -C build install-userThis installs to ~/.local/share/vlc/plugins/. Note: the Ubuntu/Debian
build of VLC does not auto-scan that directory, so you have to point VLC
at it:
VLC_PLUGIN_PATH=~/.local/share/vlc/plugins vlc u64://@:11000For convenience you may want a shell alias:
alias vlc-u64='VLC_PLUGIN_PATH=$HOME/.local/share/vlc/plugins vlc'System-wide on Linux (auto-detected by VLC, no env var needed):
VLC's distro-packaged build only scans the system plugin directory — on
Debian/Ubuntu that's /usr/lib/x86_64-linux-gnu/vlc/plugins/. Meson's
default prefix is /usr/local, which VLC will not scan, so you have to
point the install at the real plugin dir explicitly:
meson configure build \
-Dvlc_plugin_dir=/usr/lib/x86_64-linux-gnu/vlc/plugins/access
sudo meson install -C build
# Optional: regenerate VLC's plugin cache so it picks up the new module
# without scanning everything at next launch.
sudo /usr/lib/x86_64-linux-gnu/vlc/vlc-cache-gen \
/usr/lib/x86_64-linux-gnu/vlc/pluginsAfter this, vlc u64://@:11000 works from any shell with no env var.
Caveat: when apt upgrade updates the vlc-plugin-base package it
may overwrite the access/ directory and remove the file. Re-run the
meson install if that happens.
vlc -p u64streamShould print the module description and its options.
Each UDP datagram on the U64 video port carries:
off size field
0 2 sequence number (u16 LE)
2 2 frame number (u16 LE)
4 2 line number (u16 LE; bit15 = last packet of frame)
6 2 pixels per line (u16 LE; constant 384)
8 1 lines per packet (u8; constant 4)
9 1 bits per pixel (u8; constant 4)
10 2 encoding (u16; constant 0)
12 768 pixel payload (4 lines x 384 px x 4 bits, nibble-packed:
low nibble = leftmost pixel)
A frame is 68 packets (PAL, 272 lines) or 60 packets (NTSC, 240 lines). The plugin:
- Opens a UDP socket itself (single combined
access_demuxmodule). - For each datagram, blits 4 lines into a 384×height RGBA framebuffer at the
header's
lineoffset (using the line number rather than packet ordering so mild reordering doesn't corrupt the picture). - On the bit-15 (last-of-frame) packet, emits the framebuffer as a
block_twith a paced PTS, and updates the input clock viaES_OUT_SET_PCR.
Reference: Ultimate-64 data streams documentation.
Apache-2.0. See LICENSE.
- Gideon's Logic for the Ultimate 64 and the publicly documented protocol.
- DusteD's u64view (WTFPL) — clear reference C implementation that confirmed packet layout and audio framing.