As a software engineer with limited C++ expertise, I directed the development of Qt‑Caster by iteratively prompting AI language models (Claude and DeepSeek) to generate the majority of the code. Through systematic debugging, precise requirements, and continuous validation of the AI's output, I guided the project from concept to a fully functional streaming application. This process demonstrates my ability to leverage AI tools effectively while retaining full ownership of problem‑solving and architectural decisions.
Qt-Caster is a cross-platform GUI application for streaming your screen (video) and microphone/audio input (audio) over UDP using GStreamer and PipeWire (Linux) / platform‑specific capture APIs. It supports custom stream names, hardware acceleration (VAAPI, NVENC, QSV), software fallback, and persistent pinned streams that automatically start on launch.
- 🎥 Video Streaming – Capture any screen/window via PipeWire (Linux) with configurable:
- Resolution, bitrate, framerate (via encoder)
- Encoder:
x264(software),VAAPI H.264,NVENC H.264,QSV H.264 - Hardware acceleration with automatic fallback to software
- x264 tuning (
zerolatency,film, …), speed presets, profile
- 🎤 Audio Streaming – Capture any PipeWire audio source (mic, loopback) with:
- Codec:
OpusorAAC - Bitrate, channels (1–8), sample rate
- Opus application (
audio,voip,lowdelay)
- Codec:
- 📛 Custom Stream Names – Give your streams human‑readable names; otherwise auto‑named (
qt-caster-video-1, …) - 📌 Pin & Save Streams – Check the Pin column to save a stream permanently. Pinned streams are saved to
~/.config/qt-caster/saved-streams.jsonand restart automatically when the app launches. - ✏️ Edit Running Streams – Right‑click any active stream to modify its configuration (video or audio) and restart it with the new settings.
- 👁️ Video Preview – For video streams, right‑click and select “Visualize Stream” to open a separate window showing the captured video (uses the same PipeWire source).
- 🎚️ Virtual Sink (Audio) – When creating an audio stream, check “Create virtual sink” to create a PulseAudio null sink and automatically route the stream through it. This makes the audio available to other applications for further processing.
- 🖥️ Live Stream Table – View all active streams with status, destination, options, and live activity indicator (🟢/🔴)
- 🖱️ System Tray Integration – Minimize to tray, right‑click to manage streams, click to show window
- 🧹 Clean Shutdown – Gracefully terminates all GStreamer processes on quit (Ctrl+C, tray quit, window close)
- 📦 Portable AppImage (Linux) – Single‑file executable with zero runtime dependencies (bundles Qt, GStreamer, all plugins)
| Platform | Status | Notes |
|---|---|---|
| Linux (x86_64) | ✅ Fully supported | PipeWire required; AppImage available |
| macOS | 🚧 In progress | Requires GStreamer, CoreAudio, ScreenCaptureKit |
| Windows | 🚧 Planned | DirectShow / WGC, DirectSound |
- Qt6 (
Core,Widgets,Multimedia) - GStreamer 1.0+ with plugins:
gst-plugins-basegst-plugins-good(forpipewiresrc,udpsink)gst-plugins-bad(forvaapih264enc,nvh264enc,qsvh264enc– optional)gst-plugins-ugly(forx264enc)gst-libav(foravenc_aac)
- PipeWire (≥ 0.3.40) with development headers
- CMake ≥ 3.16
- C++17 compiler
Install on Arch Linux:
sudo pacman -S qt6-base qt6-multimedia gstreamer gst-plugins-base gst-plugins-good gst-plugins-bad gst-plugins-ugly gst-libav pipewire cmake base-develInstall on Ubuntu/Debian:
sudo apt install qt6-base-dev qt6-multimedia-dev libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev \
gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly gstreamer1.0-libav \
libpipewire-0.3-dev cmake build-essentialInstall on Fedora:
sudo dnf install qt6-qtbase-devel qt6-qtmultimedia-devel gstreamer1-devel gstreamer1-plugins-base-devel \
gstreamer1-plugins-good gstreamer1-plugins-bad-free gstreamer1-plugins-ugly-free \
gstreamer1-libav pipewire-devel cmake gcc-c++- Qt6 (via Homebrew or official installer)
- GStreamer (from gstreamer.freedesktop.org)
- CMake
- Qt6 (MSVC 2019/2022)
- GStreamer (MinGW or MSVC runtime)
- CMake
git clone https://github.com/Khyretos/qt-screen-caster.git
cd qt-screen-castermkdir build && cd build
cmake .. -DCMAKE_BUILD_TYPE=Releasegit clone https://github.com/Khyretos/qt-screen-caster.git
cd qt-screen-caster
mkdir build && cd build
cmake .. -DCMAKE_BUILD_TYPE=Release
make -j$(nproc)git clone https://github.com/Khyretos/qt-screen-caster.git
cd qt-screen-caster
rm -rf build
mkdir build && cd build
cmake -DCMAKE_BUILD_TYPE=Debug \
-DCMAKE_C_COMPILER=/usr/bin/clang \
-DCMAKE_CXX_COMPILER=/usr/bin/clang++ \
-G Ninja ..
ninjaAfter successful build, you will have two executables in the build/ directory:
qt-caster – Main GUI application
qt-caster-worker – Worker process (spawned by the GUI)
Qt-Caster can be bundled into a single, dependency‑free AppImage using a Docker‑based build process. This ensures the environment is consistent and reproducible.
Follow the official instructions for your distribution: https://docs.docker.com/engine/install/. Make sure the Docker daemon is running.
The repository includes a Dockerfile.appimage and a build-appimage.sh script. Simply run:
./build-appimage.shThe script builds the Docker image, compiles the application inside, and creates the AppImage. Once finished, the AppImage will be located in the dist/ folder inside the project root.
Note
The AppImage bundles everything – Qt, GStreamer, and all required plugins. It runs on any modern Linux distribution without needing to install GStreamer or Qt separately. The first build may take a few minutes as it downloads the base image and dependencies. Subsequent builds will be faster due to Docker caching.
./qt-caster # from build directory
./Qt-Caster-x86_64.AppImage # portable version-
Click 🎥 Create Video Stream or 🎤 Create Audio Stream.
-
Configure the stream parameters:
-
Video: resolution, bitrate, encoder (hardware/software), tuning options.
-
Audio: codec (Opus/AAC), bitrate, channels, sample rate, direction (outgoing/incoming).
-
-
(Optional) Enter a custom name – otherwise an auto‑name is assigned (e.g.,
qt-caster-video-1). -
For audio streams, you can optionally enable Create virtual sink and provide a custom sink name. This creates a PulseAudio null sink and automatically routes the stream through it, making the audio available to other applications.
Notej
The stream is automatically connected to the sink using pw-link, ensuring audio flows correctly. The sink's monitor source appears as a virtual microphone in applications like Discord.
-
Click ✅ Start.
-
If streaming a screen, the PipeWire portal will appear – select a screen or window to capture.
The main window displays all active streams in a table. You can:
-
Double‑click any row → stops and removes that stream.
-
Pin a stream by checking the 📌 column → the configuration is saved to
~/.config/qt-caster/saved-streams.jsonand the stream will automatically restart when the application launches. -
Right‑click a row to open a context menu with additional options:
-
✏️ Edit Stream – modify the stream’s configuration (video or audio). The stream will be restarted with the new settings.
-
👁️ Visualize Stream (video only) – opens a separate window showing the captured video (using the same PipeWire source). Useful to verify you are sharing the correct screen/window.
-
-
System Tray – right‑click the tray icon for quick actions, or hover to see a list of active streams.
When you enable “Create virtual sink” in the audio stream dialog, a PulseAudio null sink is created (e.g., qt-caster-stream-name). The audio stream is connected to that sink:
For outgoing streams, the source is the sink’s monitor (i.e., the virtual sink acts as an input source for other applications).
For incoming streams, the sink is used as the playback device, allowing you to capture the received audio separately.
You can manage the sink with standard PulseAudio tools (pactl list sinks, pavucontrol). The sink is automatically removed when the stream stops.
- Click ⏹ Quit Application – all streams are terminated gracefully.
- Press Ctrl+C in the terminal – same clean shutdown.
- Click the window close button (X) – minimizes to tray (unless quitting).
Pinned streams are stored in:
- Linux:
~/.config/qt-caster/saved-streams.json
The file is plain JSON. You can edit it manually, but it’s easier to use the Pin checkbox in the UI.
| Problem | Solution |
|---|---|
| Video stream fails with “stream error: no more input formats” | Ensure stream-properties is not in the video pipeline (it is removed in the working version). Use the pipeline provided in stream_worker.cpp. |
| Hardware encoder not available | Install system drivers (VAAPI, NVIDIA, Intel QSV). The app automatically falls back to x264. |
| AppImage doesn’t start | Ensure it’s executable (chmod +x). If on an older distribution, try building with an older GLIBC. |
| No system tray icon | Some desktop environments (e.g., GNOME) do not support system trays. The app still works; use the window directly. |
| PipeWire portal does not appear | Install xdg-desktop-portal and xdg-desktop-portal-gtk (or -kde). Restart session. |
| Virtual sink not showing in applications | Install pipewire-utils (or pipewire-bin) which provides pw-link. Ensure the stream is running and check pw-link -l to see active connections. |
Contributions are welcome! Please follow these steps:
- Fork the repository.
- Create a feature branch (git checkout -b feature/amazing-feature).
- Commit your changes (git commit -m 'Add amazing feature').
- Push to the branch (git push origin feature/amazing-feature).
- Open a Pull Request.
- Use 4 spaces for indentation.
- Follow Qt naming conventions (camelCase for methods, PascalCase for classes).
- Include emoji log messages (see existing code) for clarity.
This project is licensed under the GNU General Public License v3.0 – see the LICENSE file for details.
- GStreamer – multimedia framework
- Qt Project – cross‑platform GUI toolkit
- PipeWire – low‑latency audio/video server
- linuxdeploy – AppImage packaging tools





