Real-time Android device screen streaming to browser using scrcpy, v4l2loopback, and MJPEG.
# Using Taskfile (recommended)
task run
# Or build and run manually
go build -o avdstream .
./avdstream -device emulator-5554
# Open http://localhost:8000This project uses a modern Go-based streaming solution:
- scrcpy captures Android screen and pushes YUV420 frames to
/dev/video20(v4l2loopback virtual camera) - ffmpeg reads from
/dev/video20and encodes to MJPEG - Go HTTP server with broadcaster pattern distributes frames to multiple clients
- Browser displays MJPEG stream with low latency (~30fps)
- Low latency: ~100-300ms delay (much better than HLS)
- Multi-client support: Broadcaster pattern allows multiple viewers without restarting the stream
- Automatic lifecycle: ffmpeg starts on first client, stops 5s after last client disconnects
- Embedded web UI: No external HTTP server needed
- Continuous streaming: No 3-minute limit like adb screenrecord
┌─────────────┐
│ Android │
│ Device │
└──────┬──────┘
│ scrcpy (YUV420 30fps)
▼
┌─────────────┐
│ /dev/video20│ v4l2loopback virtual camera
└──────┬──────┘
│ reads frames
▼
┌─────────────┐
│ ffmpeg │ Encode to MJPEG
└──────┬──────┘
│ stdout pipe
▼
┌─────────────┐
│ Broadcaster │ Parse JPEG frames, distribute to clients
│ (main.go) │
└──────┬──────┘
│ HTTP /mjpeg (multipart/x-mixed-replace)
▼
┌─────────────┐
│ Browser │ Native MJPEG support
│ (index.html)│
└─────────────┘
- Go 1.25.3 or later
- scrcpy (for Android screen mirroring)
- ffmpeg with H.264 and MJPEG support
- v4l2loopback kernel module (Linux only)
- adb (Android Debug Bridge)
- Running Android device or emulator
# Install v4l2loopback (Ubuntu/Debian)
sudo apt install v4l2loopback-dkms
# Configure and load module
task setup
# Or manually:
sudo modprobe v4l2loopback video_nr=20 card_label="LoopbackCam" exclusive_caps=0# Run with default device (emulator-5554)
task run
# Run with specific device
task run DEVICE=emulator-5580
# Run without starting scrcpy (if already running)
task run-no-scrcpy
# Development mode (go run)
task dev
# List available tasks
task --list# Build
go build -o avdstream .
# Run with default device
./avdstream
# Specify device
./avdstream -device emulator-5580
# Run without auto-starting scrcpy
./avdstream -no-scrcpyThe application accepts the following flags:
-device: Android device serial (default:emulator-5554)-no-scrcpy: Don't start scrcpy automatically
Default port is :8000. Access the stream at http://localhost:8000.
avdstream/
├── main.go # Go server with broadcaster and ffmpeg integration
├── go.mod # Go module definition
├── Taskfile.yml # Task runner configuration
├── mise.toml # Tool version management
├── www/
│ ├── index.html # MJPEG player with status monitoring
│ ├── old.html # Legacy HLS player
│ └── brutto.html # Alternative player
└── README.md
The Broadcaster struct manages:
- Single ffmpeg instance for all clients
- Frame parsing from MJPEG stream (JPEG SOI 0xFFD8 to EOI 0xFFD9)
- Distribution to subscribed clients via channels
- Automatic start/stop based on client count
Serves multipart/x-mixed-replace stream:
- Client connects to
/mjpeg - Broadcaster starts ffmpeg if not running
- Client subscribes to frame channel
- Each JPEG frame sent with MIME boundary
- Client disconnects, broadcaster stops after 5s if no other clients
Simple MJPEG player with:
- Connection status indicator
- FPS counter
- Automatic reconnection on errors
- Low-latency display
No video appears:
# Check Android device is connected
adb devices
# Verify /dev/video20 exists
ls -l /dev/video20
# Test v4l2loopback
task test-stream
# Check scrcpy works
scrcpy -s emulator-5554 --no-playback --v4l2-sink=/dev/video20Stream is black or frozen:
# Restart v4l2loopback module
sudo modprobe -r v4l2loopback
sudo modprobe v4l2loopback video_nr=20 card_label="LoopbackCam" exclusive_caps=0
# Check ffmpeg can read from video20
ffmpeg -f v4l2 -i /dev/video20 -frames:v 1 test.jpgPort already in use:
# Check what's using port 8000
sudo lsof -i :8000
# Kill existing process
pkill avdstreamv4l2loopback not available (macOS/Windows):
This solution currently requires Linux with v4l2loopback. For other platforms, consider:
- Running in a Linux VM or container
- Using the legacy bash/HLS solution (see git history)
- Resolution: 1080x2400 downscaled to 540x1200 for streaming
- Frame rate: 30fps (configurable in main.go:52)
- Latency: ~100-300ms (vs 2-4s for HLS)
- MJPEG quality: q:v 3 (configurable in main.go:59)
- Multi-client: No performance impact, same ffmpeg instance shared
# Run in development mode
task dev
# Build binary
task build
# Clean build artifacts
task clean
# Check available devices
task check-deviceMIT