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.
- SH1106 - 128x64, common in 1.3" displays (default)
- SSD1306 - 128x64, common in 0.96" displays
- SSD1305 - 128x64
- SSD1309 - 128x64
- Orange Pi (tested on Orange Pi 5) or Raspberry Pi
- 0.96" - 1.3" OLED Display Module (128x64 pixels, I2C)
- I2C connection using pins:
I2C_SDA_M0(SDA/Data) orGPIO2(Raspberry Pi)I2C_SCL_M0(SCL/Clock) orGPIO3(Raspberry Pi)- 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.
- Enable I2C on Orange Pi:
# Install I2C tools and Python package manager
sudo apt-get install -y i2c-tools python3-pip
# Enable I2C using armbian-config (recommended method for Armbian-based systems)
sudo armbian-config
# Navigate to: System -> Hardware -> enable i2c0 or i2c1
# Save and reboot
# Alternative: For manual configuration, you can edit /boot/orangepiEnv.txt
# Add or uncomment the following line:
# overlays=i2c0
# Then reboot the systemFor Raspberry Pi:
# Install I2C tools
sudo apt-get install -y i2c-tools python3-pip
# Enable I2C
sudo raspi-config
# Navigate to: Interface Options -> I2C -> Enable
# Reboot the system- Verify I2C connection:
# Check if I2C device is detected (default address: 0x3C)
sudo i2cdetect -y 0 # Try bus 0
# or
sudo i2cdetect -y 1 # Try bus 1- Install Python dependencies:
Tip
Recommended installation method: Install to user directory to avoid system package conflicts.
cd x11stream
# Install base dependencies
pip3 install --user adafruit-blinka 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
# Or install all drivers at once:
pip3 install --user -r requirements.txt- Configure OLED driver (optional):
Set the driver type and I2C address 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
EOF'Available drivers: sh1106 (default), ssd1306, ssd1305, ssd1309
- Install OLED display script:
sudo cp oled_display.py /usr/local/bin/
sudo chmod +x /usr/local/bin/oled_display.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 I2C diagnostics on startup:
- Checks for I2C device nodes: Verifies
/dev/i2c-*devices exist - Scans I2C buses: Uses
i2cdetectto find OLED display at 0x3C or 0x3D - Provides helpful errors: Clear guidance if I2C is not configured
- Supports multiple drivers: Auto-detects and uses the configured driver
Example auto-check output:
Performing I2C auto-check...
âś“ Found I2C device nodes: /dev/i2c-0, /dev/i2c-1
âś“ I2C device detected on bus 0 at address 0x3C or 0x3D
Initializing SH1106 display at I2C address 0x3C...
âś“ SH1106 display initialized successfully
You can configure the OLED driver in several ways:
- Environment variable (recommended for systemd):
export OLED_DRIVER=sh1106 # or ssd1306, ssd1305, ssd1309
export I2C_ADDRESS=0x3C # or 0x3D- System-wide configuration file:
# Create /etc/default/oled_display
OLED_DRIVER=sh1106
I2C_ADDRESS=0x3C- 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 I2C is enabled:
sudo i2cdetect -y 0orsudo i2cdetect -y 1 - Check if device appears at address 0x3C or 0x3D
- 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
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
Wrong I2C bus:
Important
The script uses board.SCL and board.SDA, which are the default I2C pins defined by the board library. They do not auto-detect alternate buses or pins.
- If you're using a different I2C bus, you may need to modify the Python script
- For manual configuration, check which I2C bus your display is on with
i2cdetect - You may need to modify the script to use a different I2C bus if your hardware differs from the default configuration
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 custom configurations 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 Interface Selection
I2C_SDA_PIN = "I2C2_SDA_M0" # Orange Pi: I2C2_SDA_M0, I2C0_SDA
I2C_SCL_PIN = "I2C2_SCL_M0" # Raspberry Pi: GPIO2 (SDA), GPIO3 (SCL), or SDA/SCL
# I2C Address
I2C_ADDRESS = 0x3C # Most displays use 0x3C or 0x3D# Run the driver test
python3 drivertest.pyThe script will:
- Display configuration information (driver, text, I2C pins)
- 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 different I2C interfaces: Verify correct pin configuration
- Custom messages: Display any text on your OLED for testing
- Troubleshooting: Isolate display issues from the main service
Orange Pi 5 with 1.3" SH1106 display:
DRIVER_NAME = "sh1106"
I2C_SDA_PIN = "I2C2_SDA_M0"
I2C_SCL_PIN = "I2C2_SCL_M0"Raspberry Pi with 0.96" SSD1306 display:
DRIVER_NAME = "ssd1306"
I2C_SDA_PIN = "SDA" # or "GPIO2"
I2C_SCL_PIN = "SCL" # or "GPIO3"Custom I2C bus:
DRIVER_NAME = "ssd1309"
I2C_SDA_PIN = "I2C0_SDA"
I2C_SCL_PIN = "I2C0_SCL"The 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