One stream in, many clients out.
A lightweight Go-based UDP relay that receives ARKit-style facial tracking data from the iFacialMocap iOS app and forwards it simultaneously to multiple destinations (VBridger for VTube Studio, Warudo, etc.) without introducing measurable latency.
Perfect for VTubers who want to use the same facial tracking data in multiple applications simultaneously.
The relay forwards packets byte-for-byte to all configured targets without modification. This enables splitting iFacialMocap's single UDP stream to multiple applications simultaneously, allowing you to use the same facial tracking data in multiple programs at once.
graph TB
subgraph "iOS Device"
A[iFacialMocap App]
end
subgraph "PC/Laptop"
B["UDP Relay (Port 13121)"]
C["VBridger (Port 49983)"]
D[VTube Studio]
E["Warudo (Port 39539)"]
end
A -->|"UDP Packets (ARKit Blendshapes)"| B
B -->|Identical Copy| C
C -->|Forwarded| D
B -->|Identical Copy| E
style A fill:#4A90E2
style B fill:#50C878
style C fill:#FFB347
style D fill:#FF6B6B
style E fill:#FF6B6B
sequenceDiagram
participant iOS as iFacialMocap iOS
participant Relay as UDP Relay
participant VB as VBridger
participant VTS as VTube Studio
participant Warudo as Warudo
Note over Relay: Listening on port 13121
loop Every Frame (~30-60 FPS)
iOS->>Relay: UDP Packet (ARKit data)
Note over Relay: Packet Size: ~956-965 bytes
par Parallel Forwarding
Relay->>VB: Identical Copy
Relay->>Warudo: Identical Copy
end
VB->>VTS: Forwarded Data
Relay->>Relay: Update Statistics
end
Note over Relay: Periodic Stats Report (Every 10 seconds)
Based on packet analysis, iFacialMocap sends ARKit-compatible facial tracking data in a pipe-separated string format:
- Packet Size: ~956-965 bytes
- Format: Plain text, pipe-delimited
- Protocol: UDP
-
Blendshape Parameters (ARKit-compatible)
- Eye tracking:
eyeSquint_R&10,eyeBlink_L&26,eyeLookUp_L&0 - Mouth tracking:
mouthSmile_R&7,mouthPucker&9,mouthClose&2 - Brow tracking:
browDown_R&10,browInnerUp&8 - Cheek tracking:
cheekSquint_L&7,cheekPuff&4 - Jaw tracking:
jawOpen&2,jawForward&2 - Tracking status:
trackingStatus&1
- Eye tracking:
-
Head Rotation (6-DOF)
- Format:
head#x,y,z,rx,ry,rz - Example:
head#6.4057,-0.086789705,0.20101094,0.016818939,-0.0029828981,-0.030570496
- Format:
-
Eye Tracking (3-DOF per eye)
- Format:
rightEye#x,y,zandleftEye#x,y,z - Example:
rightEye#1.4575024,-0.26850903,-0.016194224
- Format:
trackingStatus&1|eyeSquint_R&10|eyeBlink_L&26|mouthSmile_L&5|...
=head#6.4057,-0.086789705,0.20101094,0.016818939,-0.0029828981,-0.030570496|
rightEye#1.4575024,-0.26850903,-0.016194224|
leftEye#1.4462843,-0.30282283,-0.123135895|
This relay is primarily designed for Windows. It can also be built on macOS/Linux for experimental purposes.
Install Go 1.22 or later:
Option 1: Using winget (Recommended)
winget install GoLang.GoOption 2: Manual Installation
- Download Go from https://go.dev/dl/
- Run the installer (.msi file)
- The installer will add Go to your PATH automatically
- Verify installation: Open a new Command Prompt or PowerShell and run:
go version
Option 3: Using Chocolatey
choco install golangbrew install goNote: While the relay can be built on macOS, it's primarily designed and tested for Windows. Use macOS for development/experimentation purposes.
Use your distribution's package manager:
# Debian/Ubuntu
sudo apt-get install golang-go
# Fedora/RHEL
sudo dnf install golang
# Arch Linux
sudo pacman -S go# Standard build
go build -ldflags "-s -w" -o ifmrelay.exe main.go
# Smallest binary size (recommended)
go build -ldflags "-s -w" -trimpath -o ifmrelay.exe main.goBuild Flags Explained:
-ldflags "-s -w": Strips debug symbols and reduces binary size-trimpath: Removes file system paths from the compiled binary-o ifmrelay.exe: Output filename
# Standard build
go build -ldflags "-s -w" -o ifmrelay main.go
# Smallest binary size
go build -ldflags "-s -w" -trimpath -o ifmrelay main.goPlace the compiled binary (ifmrelay.exe on Windows, ifmrelay on macOS/Linux) and relay_config.json in the same directory.
-
Edit Configuration
Edit
relay_config.jsonto match your setup:{ "listen_port": 13121, "targets": [ {"host": "127.0.0.1", "port": 49983, "name": "VBridger"}, {"host": "127.0.0.1", "port": 39539, "name": "Warudo"} ], "buffer_size": 4096, "log_level": "info", "stats_interval": 10 }Note: Port
13121is a custom port chosen to avoid conflicts with common software. It maps to Celeste themes/motifs and should not interfere with well-known services. -
Configure VBridger
Before starting the relay, configure VBridger to listen on the loopback interface:
- Open VBridger
- Set the IP address to
127.0.0.1(loopback/localhost) - Set the port to
49983(must match the port inrelay_config.json) - Click "Connect" to start listening for incoming traffic
Important: Using
127.0.0.1ensures VBridger only listens on the loopback interface, preventing external connections. This is a security best practice for local-only traffic. -
Start the Relay
.\ifmrelay.exe -config relay_config.json -
Configure iFacialMocap iOS App
- Set your PC's IP address
- Set port to
13121(or your configuredlisten_port) - IMPORTANT: After starting the relay, restart iFacialMocap to establish connection
-
Configure Windows Firewall (Windows only)
Windows Defender Firewall will likely block incoming UDP traffic. You need to allow the relay:
Option A: Allow via Windows Defender Firewall GUI
- Open Windows Defender Firewall:
Win + R, typewf.msc, press Enter - Click "Inbound Rules" → "New Rule..."
- Select "Port" → Next
- Select "UDP" and enter port
13121→ Next - Select "Allow the connection" → Next
- Check all profiles (Domain, Private, Public) → Next
- Name it "iFacialMocap UDP Relay" → Finish
Option B: Allow via PowerShell (Run as Administrator)
New-NetFirewallRule -DisplayName "iFacialMocap UDP Relay" -Direction Inbound -Protocol UDP -LocalPort 13121 -Action Allow
Option C: Allow the executable directly
- Open Windows Defender Firewall:
Win + R, typewf.msc, press Enter - Click "Inbound Rules" → "New Rule..."
- Select "Program" → Next
- Browse to
ifmrelay.exe→ Next - Select "Allow the connection" → Next
- Check all profiles → Next
- Name it "iFacialMocap UDP Relay" → Finish
- Open Windows Defender Firewall:
-
Verify
VBridger (which forwards to VTube Studio) and Warudo should receive tracking data simultaneously.
Check VBridger: Verify that VBridger shows it's receiving data and forwarding to VTube Studio. The connection status in VBridger should indicate active data flow.
When started correctly, you should see:
[INFO] Listening on :13121
[INFO] Forwarding to 127.0.0.1:49983 (VBridger)
[INFO] Forwarding to 127.0.0.1:39539 (Warudo)
[INFO] Relay started successfully
Every 10 seconds (default), statistics will be printed:
[STATS] Uptime: 1m30s | Received: 450 | Forwarded: 900 | Dropped: 0 | Avg Latency: 0.123 ms
| Option | Type | Default | Description |
|---|---|---|---|
listen_port |
int | 13121 | UDP port to listen for iFacialMocap data (custom port, Celeste-themed, avoids conflicts) |
targets |
array | required | List of forwarding destinations |
buffer_size |
int | 4096 | Buffer size in bytes for packet handling |
log_level |
string | "info" | Logging level: debug, info, or error |
stats_interval |
int | 10 | Seconds between statistics reports |
Each target requires:
host: IP address or hostnameport: UDP port numbername: Friendly name for logging
Cause: iFacialMocap needs to be restarted after relay starts, or wrong port/firewall
Fix:
- RESTART iFacialMocap after starting the relay (it needs to reconnect)
- Verify iFacialMocap target IP/port matches relay
listen_port(default: 13121) - Windows Firewall: Add firewall exception for UDP port 13121 (inbound) - see "Windows Firewall Configuration" section below
- Check Windows Defender Firewall settings
- Verify the relay is actually listening: Use
netstat -an | findstr :13121to confirm
Cause: One target port is incorrect, or VBridger is not properly configured
Fix:
- VBridger Configuration:
- Ensure VBridger is set to
127.0.0.1(not your network IP) - Verify VBridger port is
49983(must matchrelay_config.json) - Important: VBridger must be connected (click "Connect" button) before starting the relay
- Check VBridger connection status indicator to confirm it's listening
- Ensure VBridger is set to
- Port Verification:
- Verify VBridger/Warudo ports in
relay_config.jsonmatch actual ports - Check if VBridger and Warudo are listening on expected ports
- Use
netstat -an | findstr :49983to verify VBridger port - Use
netstat -an | findstr :39539to verify Warudo port
- Verify VBridger/Warudo ports in
Cause: System resource constraints or network issues
Fix:
- Use wired Ethernet connection (avoid WiFi)
- Check CPU/GPU usage during streaming
- Limit OBS encoder settings if streaming
Cause: Network congestion or buffer overflow
Fix:
- Use wired Ethernet connection (avoid WiFi)
- Increase
buffer_sizein config if using custom payloads - Ensure relay has adequate system resources
Windows Defender Firewall will block incoming UDP connections by default. You must configure a firewall rule to allow the relay to receive packets from your iOS device.
- Default Listen Port:
13121(UDP) - Port Selection: Custom port chosen to avoid conflicts with well-known software
- Protocol: UDP (inbound)
# Allow UDP port 13121 inbound
New-NetFirewallRule -DisplayName "iFacialMocap UDP Relay" -Direction Inbound -Protocol UDP -LocalPort 13121 -Action Allow -Profile Any- Press
Win + R, typewf.msc, press Enter - In Windows Defender Firewall, click "Inbound Rules" in the left pane
- Click "New Rule..." in the right pane
- Select "Port" → Click Next
- Select "UDP" and enter
13121→ Click Next - Select "Allow the connection" → Click Next
- Check all profiles (Domain, Private, Public) → Click Next
- Name it "iFacialMocap UDP Relay" → Click Finish
# Check if rule exists
Get-NetFirewallRule -DisplayName "iFacialMocap UDP Relay"
# Check firewall rules for port 13121
netsh advfirewall firewall show rule name=all | findstr "13121"If you prefer to allow the executable instead of the port:
- Press
Win + R, typewf.msc, press Enter - Click "Inbound Rules" → "New Rule..."
- Select "Program" → Click Next
- Browse to your
ifmrelay.exe→ Click Next - Select "Allow the connection" → Click Next
- Check all profiles → Click Next
- Name it "iFacialMocap UDP Relay" → Click Finish
After configuring the firewall:
# Test if port is listening (should show UDP :13121)
netstat -an | findstr :13121
# Test connectivity from another device (if available)
# Replace 192.168.1.100 with your PC's IP address
Test-NetConnection -ComputerName 192.168.1.100 -Port 13121 -UdpSymptom: No packets received, relay shows no activity
Diagnosis:
- Check if firewall rule exists and is enabled
- Verify rule allows UDP protocol (not TCP)
- Ensure rule applies to the correct network profile (Private/Public)
- Check if another firewall/antivirus is blocking
Commands:
# View all firewall rules for this relay
Get-NetFirewallRule -DisplayName "*iFacialMocap*" | Get-NetFirewallPortFilter
# Check if Windows Defender Firewall is enabled
Get-NetFirewallProfile | Select-Object Name, Enabled
# Temporarily disable firewall for testing (NOT RECOMMENDED FOR PRODUCTION)
Set-NetFirewallProfile -Profile Domain,Public,Private -Enabled False- CPU usage: <0.5%
- Memory usage: <15 MB
- Average latency: <1 ms
- Packet loss: <0.1%
Using START /B:
START /B .\ifmrelay.exe -config relay_config.jsonAs Windows Service (requires NSSM):
nssm install IFMRelay "C:\IFMRelay\ifmrelay.exe" -config "C:\IFMRelay\relay_config.json"Using nohup:
nohup ./ifmrelay -config relay_config.json > relay.log 2>&1 &As systemd service:
Create /etc/systemd/system/ifmrelay.service:
[Unit]
Description=iFacialMocap UDP Relay
After=network.target
[Service]
Type=simple
User=yourusername
WorkingDirectory=/path/to/ifmrelay
ExecStart=/path/to/ifmrelay -config relay_config.json
Restart=on-failure
[Install]
WantedBy=multi-user.targetThen:
sudo systemctl enable ifmrelay
sudo systemctl start ifmrelayWindows (Command Prompt or PowerShell):
# Send a test packet
echo test | nc -u 127.0.0.1 13121macOS/Linux:
# Send a test packet
echo "test" | nc -u 127.0.0.1 13121Note: On Windows, you may need to install a tool like ncat (from Nmap) or use PowerShell's Test-NetConnection for UDP testing. Alternatively, use iFacialMocap itself to test.
Windows PowerShell:
1..10000 | ForEach-Object { echo $_ | nc -u 127.0.0.1 13121 }Windows Command Prompt (batch):
for /L %i in (1,1,10000) do @echo %i | nc -u 127.0.0.1 13121macOS/Linux:
for i in {1..10000}; do echo $i | nc -u 127.0.0.1 13121; doneWindows:
- Task Manager (CPU/Memory usage)
- Resource Monitor (Network activity, view with
resmon.exe) - Relay statistics (periodic reports in console)
macOS/Linux:
toporhtopfor CPU/Memorynetstatorssfor network activity- Relay statistics (periodic reports in console)
| Level | Description |
|---|---|
debug |
Prints every received packet size and timestamp |
info |
Standard operation logs + periodic statistics (default) |
error |
Only errors and warnings |
-config <path> Path to configuration file (default: relay_config.json)
- Packet Forwarding: Byte-for-byte, no modification
- Latency: Sub-millisecond (typically <1ms)
- Protocol: UDP (connectionless, fire-and-forget)
- Concurrency: Parallel forwarding to all targets
- Buffer Size: Configurable (default 4KB, supports up to 64KB socket buffers)
- Port Selection: The default port
13121is a custom port chosen to avoid conflicts with common software. It maps to Celeste themes/motifs and should not interfere with well-known services or applications. - The relay forwards packets exactly as received (no format conversion)
- VBridger forwards data to VTube Studio (VBridger acts as a bridge between the relay and VTube Studio)
- Warudo receives data directly from the relay
- Each target receives an identical copy of every packet
- No packet transformation or protocol translation occurs
- Windows Users: Don't forget to configure Windows Firewall to allow UDP port 13121 (inbound) - see the "Windows Firewall Configuration" section
- Packet replay tooling: Enable dumping (see below) if you plan to archive raw blendshape streams or build a replay bot.
Set dump_packets to true in relay_config.json (or launch with --dump-packets) to write every raw, pipe-delimited payload into timestamped files under raw_packets/ (default) or your custom dump_dir. Example:
{
"listen_port": 13121,
"dump_packets": true,
"dump_dir": "raw_packets",
"targets": [
{"host": "127.0.0.1", "port": 49983, "name": "VBridger"}
]
}CLI overrides:
./ifmrelay -config relay_config.json --dump-packets --dump-dir=/tmp/raw_packetsEach file contains the exact UDP payload (UTF-8 text) so you can inspect blendshape sequences or feed them into a replay engine later.
- Date: 2025-01-26
- Environment: macOS (Go 1.22.3)
- Commands:
$ go test ./...
ok ifmrelay 0.47s
$ go build ./...
# (no output, build succeeded)
This confirms the relay compiles cleanly on macOS. Windows builders should repeat the same commands (and capture go build output) before tagging a release.
- Version: 2.0 (Simple traffic splitter)
- Build: Go 1.21+
- License: Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International - See LICENSE file for details
For issues, check:
- Windows Event Viewer for system errors
- Relay log output for specific error messages
- Verify network connectivity between iOS device and PC
- Ensure target applications are running and listening
Copyright (c) 2025 whyKusanagi
This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.
Key Points:
- ✅ Free to use, modify, and distribute for non-commercial purposes
- ✅ Must provide attribution (credit the original author)
- ✅ Modified works must use the same license (ShareAlike)
- ❌ Commercial use is prohibited
⚠️ No warranty or liability - software provided "AS IS"- 📄 See LICENSE file for full terms
Summary: Use this software freely for personal, educational, or other non-commercial purposes. Commercial use requires separate licensing. Always provide attribution when sharing or modifying.
