Shell script on Ubuntu to auto-stream X11 display on boot using x11grab and ffmpeg with HLS (HTTP Live Streaming).
- Captures X11 display using ffmpeg with ultra-low-latency settings (400-600ms)
- HLS streaming for multiple concurrent connections without crashes
- Hardware acceleration support for Rockchip RK3588 (rkmpp), VAAPI, and Intel QSV
- Hosts an HTTP server for direct browser/VLC/mobile access
- Auto-starts on boot via systemd service with automatic restart on failures
- Interactive mode for easy configuration
- Audio streaming support with multiple quality presets
- Auto-detect resolution with intelligent scaling (scales down if display is larger than target)
- Configurable resolution, framerate, bitrate, and audio settings
- Bandwidth estimation for all configurations
- OLED display support for Orange Pi and Raspberry Pi (SH1106, SSD1306, SSD1305, SSD1309)
- Multi-driver support - works with common I2C OLED displays
- No crash on client disconnect: HLS streaming supports multiple clients and continues running when clients disconnect
- Ultra-low latency: Optimized to 400-600ms latency (down from 3-4 seconds)
- Hardware acceleration: Automatic detection and use of Rockchip MPP, VAAPI, or QSV when available
- Better quality: Improved encoding settings reduce compression artifacts
- Stable streaming: DTS discontinuity issues resolved with proper frame timing
- Auto-restart: Systemd service automatically restarts on any failures
- Ubuntu (or other Linux with X11)
- ffmpeg with x11grab support
- PulseAudio or ALSA (for audio streaming)
- systemd (for auto-start on boot)
The easiest way to get started is using the quickstart script:
git clone https://github.com/maple-underscore/x11stream.git
cd x11stream
./quickstart.shThe quickstart script will:
- Install all required dependencies (ffmpeg, i2c-tools, x11-utils, Python packages)
- Set up OLED display support with your choice of driver (optional)
- Install and configure systemd services
- Guide you through the setup process interactively
If you prefer to install manually or the quickstart script doesn't work for your system:
Install ffmpeg:
sudo apt update
sudo apt install ffmpegInstall x11-utils:
sudo apt-get install x11-utilsNote
Configure ffmpeg to use x11grab and libx264:
sudo ./configure --enable-x11grab --enable-libx264Use the automated quickstart script:
git clone https://github.com/maple-underscore/x11stream.git
cd x11stream
./quickstart.shThe script will guide you through the installation process and configure everything automatically.
- Clone this repository:
git clone https://github.com/maple-underscore/x11stream.git
cd x11stream- Install the script:
sudo cp x11stream.sh /usr/local/bin/
sudo chmod +x /usr/local/bin/x11stream.sh- Install and enable the systemd service:
sudo cp x11stream.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable x11stream.service
sudo systemctl start x11stream.serviceRun in interactive mode to configure all settings through a menu:
./x11stream.sh --interactive
# or
./x11stream.sh -i./x11stream.shOnce the script is running, access the stream via:
- Browser:
http://<your-ip>:8080/stream.m3u8 - VLC: Open Network Stream →
http://<your-ip>:8080/stream.m3u8 - Mobile: Most modern browsers and video players support HLS streams
# Start the service
sudo systemctl start x11stream.service
# Stop the service
sudo systemctl stop x11stream.service
# Check status
sudo systemctl status x11stream.service
# View logs
sudo journalctl -u x11stream.service -fThe x11stream project supports displaying the local IP address and streaming status on I2C OLED displays with multiple driver options. The display connects via CP2112 USB-to-I2C bridge with optional TCA9548A multiplexer support.
- SH1106 - 128x64, common in 1.3" displays (default)
- SSD1306 - 128x64, common in 0.96" displays
- SSD1305 - 128x64
- SSD1309 - 128x64
- Any system with USB port (Linux, Orange Pi, Raspberry Pi, etc.)
- CP2112 USB-to-I2C Bridge - Silicon Labs USB to I2C/SMBus bridge
- 0.96" - 1.3" OLED Display Module (128x64 pixels, I2C)
- Optional: TCA9548A I2C Multiplexer (if you need to connect multiple I2C devices)
- I2C connection from CP2112 to OLED:
- SDA (Data)
- SCL (Clock)
- VCC (3.3V-5V)
- GND
The easiest way is to use the quickstart script which will guide you through driver selection:
./quickstart.shWhen prompted, choose to install OLED support and select your driver type.
- Connect CP2112 USB-to-I2C Bridge:
# Connect CP2112 to your system via USB
# The device should appear as a HID device (no driver installation needed on Linux)
# Verify CP2112 device is detected
lsusb | grep "10c4:ea90" # Should show Silicon Labs CP2112 HID SMBus Bridge
# Set up udev rules for non-root access (optional but recommended)
sudo tee /etc/udev/rules.d/99-cp2112.rules << EOF
# CP2112 HID USB-to-SMBus Bridge
# Add user to plugdev group for access: sudo usermod -a -G plugdev \$USER
SUBSYSTEM=="hidraw", ATTRS{idVendor}=="10c4", ATTRS{idProduct}=="ea90", GROUP="plugdev", MODE="0660"
EOF
sudo udevadm control --reload-rules
sudo udevadm trigger
# Add your user to the plugdev group (replace $USER with your username if needed)
sudo usermod -a -G plugdev $USER
# Note: You'll need to log out and back in for group membership to take effect- Connect hardware:
# Wire the OLED display to CP2112:
# - OLED VCC to 3.3V or 5V (depending on your OLED)
# - OLED GND to GND
# - OLED SDA to CP2112 SDA
# - OLED SCL to CP2112 SCL
# If using TCA9548A multiplexer:
# - TCA9548A VCC to 3.3V or 5V
# - TCA9548A GND to GND
# - TCA9548A SDA to CP2112 SDA
# - TCA9548A SCL to CP2112 SCL
# - OLED SDA to one of TCA9548A SD0-SD7
# - OLED SCL to corresponding TCA9548A SC0-SC7- Install Python dependencies:
Tip
Recommended installation method: Install to user directory to avoid system package conflicts.
cd x11stream
# Install CP2112 and base dependencies
pip3 install --user cp2112 Pillow
# Install driver for your display (choose one or install all):
pip3 install --user adafruit-circuitpython-sh1106 # For SH1106 (default)
pip3 install --user adafruit-circuitpython-ssd1306 # For SSD1306
pip3 install --user adafruit-circuitpython-ssd1305 # For SSD1305
pip3 install --user adafruit-circuitpython-ssd1309 # For SSD1309
# Optional: Install TCA9548A multiplexer support
pip3 install --user adafruit-circuitpython-tca9548a
# Or install all dependencies at once:
pip3 install --user -r requirements.txt- Configure OLED driver and multiplexer (optional):
Set the driver type, I2C address, and multiplexer settings via environment variables:
# Create environment file
sudo mkdir -p /etc/default
sudo bash -c 'cat > /etc/default/oled_display << EOF
OLED_DRIVER=sh1106
I2C_ADDRESS=0x3C
# Optional: Enable TCA9548A multiplexer
USE_MULTIPLEXER=false
MULTIPLEXER_ADDRESS=0x70
MULTIPLEXER_CHANNEL=0
EOF'Available drivers: sh1106 (default), ssd1306, ssd1305, ssd1309
- Install OLED display scripts:
sudo cp oled_display.py /usr/local/bin/
sudo cp cp2112_i2c_bus.py /usr/local/bin/
sudo chmod +x /usr/local/bin/oled_display.py
sudo chmod +x /usr/local/bin/cp2112_i2c_bus.py- Install and enable the OLED display service:
sudo cp oled_display.service /etc/systemd/system/oled_display.service
# If you created an environment file, update the service to use it:
if ! sudo grep -q "EnvironmentFile" /etc/systemd/system/oled_display.service; then
sudo sed -i '/^\[Service\]/a EnvironmentFile=-/etc/default/oled_display' /etc/systemd/system/oled_display.service
fi
sudo systemctl daemon-reload
sudo systemctl enable oled_display.service
sudo systemctl start oled_display.service# Start the OLED display service
sudo systemctl start oled_display.service
# Stop the OLED display service
sudo systemctl stop oled_display.service
# Check status
sudo systemctl status oled_display.service
# View logs
sudo journalctl -u oled_display.service -fThe OLED display shows:
- Header: "X11 Stream"
- IP Address: Current local IP address (updates every 5 seconds)
- Status: Streaming status ("Streaming", "Stopped", or "Unknown")
Note
The script automatically performs CP2112 USB-to-I2C diagnostics on startup:
- Checks for CP2112 USB devices: Verifies CP2112 bridge is connected and accessible
- Initializes USB-to-I2C bridge: Configures the CP2112 for I2C communication
- Supports TCA9548A multiplexer: Optionally uses I2C multiplexer for multiple devices
- Provides helpful errors: Clear guidance if CP2112 is not found or configured
- Supports multiple drivers: Auto-detects and uses the configured driver
Example auto-check output:
Performing CP2112 USB-to-I2C auto-check...
âś“ Found 1 CP2112 USB-to-I2C bridge device(s)
Device 0: /dev/hidraw0
Initializing SH1106 display at I2C address 0x3C...
Using CP2112 device: /dev/hidraw0
âś“ SH1106 display initialized successfully
You can configure the OLED driver and multiplexer in several ways:
- Environment variable (recommended for systemd):
export OLED_DRIVER=sh1106 # or ssd1306, ssd1305, ssd1309
export I2C_ADDRESS=0x3C # or 0x3D
export USE_MULTIPLEXER=false # Set to true to enable TCA9548A
export MULTIPLEXER_ADDRESS=0x70 # TCA9548A address (usually 0x70)
export MULTIPLEXER_CHANNEL=0 # Channel 0-7 on TCA9548A- System-wide configuration file:
# Create /etc/default/oled_display
OLED_DRIVER=sh1106
I2C_ADDRESS=0x3C
USE_MULTIPLEXER=false
MULTIPLEXER_ADDRESS=0x70
MULTIPLEXER_CHANNEL=0- Edit the Python script directly (not recommended):
Edit
/usr/local/bin/oled_display.pyand change the default values.
Display not working:
Tip
Troubleshooting checklist:
- Verify CP2112 is connected:
lsusb | grep "10c4:ea90" - Check USB device permissions (may need udev rules for non-root access)
- Verify wiring connections (SDA, SCL, VCC, GND)
- Check service logs:
sudo journalctl -u oled_display.service -f - Verify correct driver is selected (check logs for driver name)
- Try different driver if display shows garbled output
- If using TCA9548A, verify multiplexer address and channel settings
Wrong driver selected:
Tip
If the display shows garbled output or doesn't work:
- Check which driver your display uses (consult display documentation)
- Common 0.96" displays use SSD1306
- Common 1.3" displays use SH1106
- Update driver:
sudo nano /etc/default/oled_displayand changeOLED_DRIVER - Restart service:
sudo systemctl restart oled_display.service
CP2112 USB device not found:
Important
The script uses CP2112 USB-to-I2C bridge for I2C communication. The CP2112 device should be automatically detected when connected via USB.
- If CP2112 is not detected, check USB connection and permissions
- You may need to set up udev rules for non-root access to the HID device
- If using TCA9548A multiplexer, ensure the multiplexer address and channel are configured correctly
- The multiplexer allows you to connect multiple I2C devices with the same address
The update.sh script allows you to easily update your x11stream installation to the latest version:
./update.shThe update script will:
- Check for uncommitted changes and offer to stash them
- Fetch the latest changes from the remote repository
- Pull and merge updates to your current branch
- Notify you if
requirements.txtor service files were updated - Provide instructions for updating dependencies or reloading services if needed
The drivertest.py script allows you to test OLED displays with CP2112 USB-to-I2C bridge and optional TCA9548A multiplexer without modifying the main display service.
Edit the configuration section at the top of drivertest.py:
# OLED Driver Selection
DRIVER_NAME = "sh1106" # sh1106, ssd1306, ssd1305, or ssd1309
# Text to display
DISPLAY_TEXT = "Hello World!"
# I2C Address
I2C_ADDRESS = 0x3C # Most displays use 0x3C or 0x3D
# TCA9548A Multiplexer Configuration
USE_MULTIPLEXER = False # Set to True to use TCA9548A multiplexer
MULTIPLEXER_ADDRESS = 0x70 # I2C address of TCA9548A
MULTIPLEXER_CHANNEL = 0 # Channel on TCA9548A (0-7)# Run the driver test
python3 drivertest.pyThe script will:
- Display configuration information (driver, text, multiplexer settings)
- Detect and connect to CP2112 USB-to-I2C bridge
- Initialize TCA9548A multiplexer if enabled
- Initialize the selected OLED driver
- Clear the display
- Show your custom text
- Keep the display on until you press Ctrl+C
- Test different drivers: Quickly test which driver works with your display
- Test CP2112 connection: Verify USB-to-I2C bridge is working
- Test multiplexer: Verify TCA9548A multiplexer configuration
- Custom messages: Display any text on your OLED for testing
- Troubleshooting: Isolate display issues from the main service
Standard setup with SH1106 display:
DRIVER_NAME = "sh1106"
I2C_ADDRESS = 0x3C
USE_MULTIPLEXER = FalseWith TCA9548A multiplexer on channel 2:
DRIVER_NAME = "ssd1306"
I2C_ADDRESS = 0x3C
USE_MULTIPLEXER = True
MULTIPLEXER_ADDRESS = 0x70
MULTIPLEXER_CHANNEL = 2Multiple displays on different channels:
# For display on channel 0
DRIVER_NAME = "ssd1306"
I2C_ADDRESS = 0x3C
USE_MULTIPLEXER = True
MULTIPLEXER_CHANNEL = 0
# For display on channel 1 (edit and run again)
# MULTIPLEXER_CHANNEL = 1The following environment variables can be set to customize the stream:
| Variable | Default | Description |
|---|---|---|
| DISPLAY | :0.0 | X11 display to capture |
| RESOLUTION | 1920x1080 | Target resolution (auto-detects and adjusts) |
| FRAMERATE | 60 | Frames per second |
| BITRATE | 6M | Video bitrate |
| HTTP_PORT | 8080 | HTTP server port |
| HTTP_BIND | 0.0.0.0 | HTTP server bind address (0.0.0.0 or 127.0.0.1) |
| USE_HARDWARE_ACCEL | auto | Hardware acceleration (auto, rkmpp, vaapi, qsv, none) |
| HLS_TIME | 1 | HLS segment duration in seconds |
| HLS_LIST_SIZE | 3 | Number of segments to keep in playlist |
| AUDIO_ENABLED | false | Enable audio streaming |
| AUDIO_BITRATE | 128 | Audio bitrate (kbps) |
| AUDIO_CODEC | aac | Audio codec (aac, mp3, pcm) |
| AUDIO_SAMPLE_RATE | 44100 | Sample rate for PCM audio |
| AUDIO_BIT_DEPTH | 16 | Bit depth for PCM audio |
The script automatically detects available hardware acceleration:
- Rockchip RK3588/MPP: Best for Orange Pi 5/5 Plus - uses
h264_rkmppencoder - VAAPI: For AMD and Intel GPUs on Linux - uses
h264_vaapiencoder - Intel QSV: For Intel CPUs with Quick Sync - uses
h264_qsvencoder - Software: Falls back to
libx264if no hardware acceleration is available
To force a specific encoder:
export USE_HARDWARE_ACCEL=rkmpp # or vaapi, qsv, noneThe HTTP server binds to 0.0.0.0 by default, making the stream accessible from all network interfaces. For enhanced security:
# Restrict to localhost only
export HTTP_BIND=127.0.0.1
# Or restrict to specific interface
export HTTP_BIND=192.168.1.100For production use with sensitive data, consider:
- Using a reverse proxy (nginx) with authentication
- Setting up a VPN for remote access
- Using firewall rules to restrict access
Note
The script automatically detects your display resolution using xdpyinfo:
- If detected resolution is higher than the target: captures full screen and scales down to target
- If detected resolution is lower than the target: uses the native (lower) resolution
- If resolution cannot be detected: uses the configured target resolution
When using interactive mode, you can choose from these audio presets:
| Preset | Bitrate | Quality | Bandwidth |
|---|---|---|---|
| 1 | 64 kbps | Voice/Low | ~8 KB/s |
| 2 | 128 kbps | Standard | ~16 KB/s |
| 3 | 192 kbps | Good | ~24 KB/s |
| 4 | 256 kbps | High | ~32 KB/s |
| 5 | 320 kbps | Maximum | ~40 KB/s |
| Preset | Sample Rate | Quality | Bandwidth |
|---|---|---|---|
| 6 | 44.1 kHz | CD quality | ~172 KB/s |
| 7 | 48 kHz | DVD quality | ~188 KB/s |
| 8 | 96 kHz | Hi-Res | ~375 KB/s |
| 9 | 192 kHz | Ultra Hi-Res | ~750 KB/s |
| Preset | Sample Rate | Quality | Bandwidth |
|---|---|---|---|
| 10 | 44.1 kHz | Studio | ~258 KB/s |
| 11 | 48 kHz | Professional | ~281 KB/s |
| 12 | 96 kHz | Hi-Res Studio | ~563 KB/s |
| 13 | 192 kHz | Ultra Hi-Res Studio | ~1125 KB/s |
| Bitrate | Quality | Bandwidth |
|---|---|---|
| 2M | Low bandwidth | ~250 KB/s |
| 4M | Medium quality | ~500 KB/s |
| 6M | Good quality | ~750 KB/s |
| 10M | High quality | ~1.25 MB/s |
| 15M | Very high | ~1.9 MB/s |
| 20M | Excellent | ~2.5 MB/s |
To modify these settings permanently, edit the service file:
sudo systemctl edit x11stream.serviceOr edit /etc/systemd/system/x11stream.service directly.
Warning
Ensure X11 is running and the DISPLAY variable is set correctly. On systems with multiple displays, try :1.0 or :0.1.
Warning
The user running the script needs access to the X11 display. Run with appropriate permissions or add the user to the video group.
Tip
Common solutions:
- Check if ffmpeg is running:
ps aux | grep ffmpeg - Ensure the port is not blocked by firewall:
sudo ufw allow 8080/tcp - Verify the IP address and port in the startup output
Tip
Troubleshooting steps:
- Ensure PulseAudio or ALSA is running
- Check audio source:
pactl list short sources(PulseAudio) - Try different audio source in interactive mode
MIT License