This project is an industrial-grade multi-camera video streaming system that combines a local Windows Forms control interface and a Self-Hosted ASP.NET Core Kestrel web server. It is designed to stream high-resolution images from Basler GigE industrial cameras over the network with ultra-low latency (WebRTC <500ms, RTSP <1s), facilitating consumption by web front-ends, image processing algorithms (e.g., OpenCV, Python), or third-party streaming servers.
┌────────────────────────────────────────┐
│ Windows Forms Local Control │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ MainForm UI │ │ SettingsForm │ │
│ └──────┬───────┘ └──────┬───────┘ │
└────────┼─────────────────────┼─────────┘
│ ┌───────────────┐ │
└─►│ Modules.Config│◄─┘
└───────┬───────┘
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ Kestrel Background Web Server (Port 5000) │
│ ┌─────────────────────────────────┐ ┌─────────────────────────────────┐ │
│ │ ApiController (REST API) │ │ StreamHub (SignalR Hub) │ │
│ │ - Status, network interfaces, │ │ - Broadcasts real-time system │ │
│ │ reloading & saving configs │ │ logs to Web SPA │ │
│ └─────────────────────────────────┘ └─────────────────────────────────┘ │
└──────────────────────────────────────┬──────────────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ wwwroot (SPA Web Dashboard) │
│ ┌─────────────────────────────────┐ ┌─────────────────────────────────┐ │
│ │ Dashboard (WHEP Player) │ │ Settings (Global & Camera) │ │
│ └─────────────────────────────────┘ └─────────────────────────────────┘ │
└──────────────────────────────────────┬──────────────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ Camera Pipeline Manager (CameraManager) │
│ ┌───────────────────────────────────────────────────────────────────────┐ │
│ │ CameraStreamPipeline (One thread per IP) │ │
│ │ ┌────────────┐ ┌────────────┐ ┌────────────┐ ┌──────────────┐ │ │
│ │ │ Pylon │──►│ RGB / Mono │──►│ FFmpeg │──►│ MediaMTX │ │ │
│ │ │ Grabbing │ │ Zero-Copy │ │ Stdin Pipe │ │ RTSP/WebRTC │ │ │
│ │ │ (64 Buffer)│ │ Conversion │ │ │ │ │ │ │
│ │ └────────────┘ └────────────┘ └────────────┘ └──────────────┘ │ │
│ └───────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
- Dual-Core Architecture:
- WinForms and Web API / WebSockets share a global state
Modules. It can be controlled locally via windows or managed and previewed remotely via a web control dashboard.
- WinForms and Web API / WebSockets share a global state
- Highly Robust Grab Loop:
- Uses Pylon SDK's
RetrieveResultinfinite grab mode to prevent interruption after capturing a fixed number of frames. - Driver buffer pool is expanded to
64buffers (PLStream.MaxNumBuffer), using C#Span<T>andArrayPoolfor pixel-level operations to absorb CPU/GC pauses and prevent packet drops. - Automatically throws an exception to release resources when a disconnect or null
RetrieveResultis detected, and attempts to reconnect automatically after 5 seconds.
- Uses Pylon SDK's
- FFmpeg Hardware-Accelerated Encoding:
- Supports H.264 and H.265 codecs.
- Supports three encoder modes: CPU (Software), NVIDIA GPU (NVENC), and Intel QuickSync (QSV).
- GOP Optimization (WebRTC Sub-second Startup):
- Removed
-tune zerolatency(which uses periodic intra-refresh that freezes Chrome's WebRTC decoder). - Explicitly configures
-g {TargetFps} -bf 0to force a discrete keyframe (I-frame) every 1 second without B-frames. WebRTC loads and displays video within 1 second with latency under 500ms.
- Removed
- Network Interface Binding & Isolation:
- Allows specifying a particular network interface IP (e.g.,
192.168.1.56). - Streaming Traffic (MediaMTX) is strictly bound to this IP, isolating the camera network from the general office network.
- Management Interface (Kestrel) continues to listen on
0.0.0.0, ensuring that loopback (localhost:5000) and external real-world IP connections remain fully accessible.
- Allows specifying a particular network interface IP (e.g.,
When you download/clone this repository and plan to compile and execute it on a completely new Windows machine, make sure to configure it as follows:
- This project is built on .NET 8.0.
- Visit Microsoft Official .NET Download Page and install .NET SDK 8.0.
- This project statically references the assembly file located in the local Pylon 6 installation directory within
PylonStream.csproj:C:\Program Files\Basler\pylon 6\Development\Assemblies\Basler.Pylon\x64\Basler.Pylon.dll - Steps to resolve:
- Download and install Basler pylon Camera Software Suite for Windows (Version pylon 6 is recommended).
- Verify that
Basler.Pylon.dllexists in the above default path. - (Note: If you use pylon 7 or other versions, please manually remove and re-add the
Basler.Pylonreference in Visual Studio, or edit the<HintPath>in the.csprojfile to match your installed version).
Since these executable files are large and frequently updated, they are excluded from this Git repository. You need to download them manually:
- FFmpeg:
- Go to FFmpeg Gyan.dev Build or the GitHub Release Page to download the Windows 64-bit static build (e.g.,
ffmpeg-git-full.7z). - Extract and retrieve
ffmpeg.exe.
- Go to FFmpeg Gyan.dev Build or the GitHub Release Page to download the Windows 64-bit static build (e.g.,
- MediaMTX:
- Go to the MediaMTX GitHub Releases page to download the Windows amd64 version (
mediamtx_vX.Y.Z_windows_amd64.zip). - Extract and retrieve
mediamtx.exe.
- Go to the MediaMTX GitHub Releases page to download the Windows amd64 version (
- Open PowerShell in the project root directory and compile the application:
dotnet build
- Navigate to the compiled output directory (default path is
bin\Debug\net8.0-windows\). - Manually create a folder named
Binariesinside that directory. - Copy the downloaded
ffmpeg.exeandmediamtx.exeinto the newly createdBinaries/folder. (The final directory structure should look exactly like this):PylonStream/ └── bin/ └── Debug/ └── net8.0-windows/ ├── PylonStream.exe ├── wwwroot/ <-- (Static Web App assets, copied automatically) └── Binaries/ <-- (Manually Created) ├── ffmpeg.exe <-- (Manually Placed) └── mediamtx.exe <-- (Manually Placed)
- Double-click to execute
PylonStream.exe. - Click 「Settings...」 on the main WinForms window:
- Configure your camera's physical IP addresses.
- Choose your camera-facing network card IP in the Bind Network Interface dropdown.
- Click 「Save Configuration」 to persist settings. A
config.jsonfile will be generated automatically in the application directory.
To ensure stable data transmission and zero packet drops (Incomplete Grab) for high-resolution GigE cameras (such as 5MP / 2448x2048) in multi-camera configurations, perform the following settings on your Windows machine:
- Open Device Manager ➡️ expand Network Adapters ➡️ right-click the network card connected to the cameras ➡️ select Properties.
- Under the Advanced tab:
- Jumbo Packet (Jumbo Frames): Set to 9014 Bytes or 9000 Bytes (if supported by network card and switch). Match this value in the application's camera Settings (
Packet Size). - Receive Buffers: Set to the maximum supported value (e.g., 2048 or 4096).
- Energy Efficient Ethernet (Green/Eco Ethernet): Set to Disabled to prevent the network card from sleeping during low-frame rate operations, causing stream dropouts.
- Jumbo Packet (Jumbo Frames): Set to 9014 Bytes or 9000 Bytes (if supported by network card and switch). Match this value in the application's camera Settings (
- Access control dashboard via browser:
- Navigate to:
http://localhost:5000(orhttp://<C#_Server_IP>:5000over local network).
- Navigate to:
- Start Services:
- Click the 「▶ Start Service」 button on the top-right of the web page.
- Wait for the cameras' status to turn green
Grabbing.
- Live Web Preview:
- Click on any camera row in the table, the Live WHEP Preview panel on the right will automatically establish connection and stream the live WebRTC video!
- Format:
rtsp://<C#_Server_IP>:8554/<StreamName> - Python OpenCV Connection Example:
import cv2 # Connect to cam1 stream cap = cv2.VideoCapture("rtsp://192.168.1.56:8554/cam1") while cap.isOpened(): ret, frame = cap.read() if not ret: break cv2.imshow('Live Camera 1', frame) if cv2.waitKey(1) & 0xFF == ord('q'): break cap.release() cv2.destroyAllWindows()
- Format:
http://<C#_Server_IP>:8889/<StreamName> - Web pages can embed this link inside an
<iframe>. The built-in player inside MediaMTX will handle WebRTC SDP signaling and render video out-of-the-box.
Third-party systems can query and control the streamer using standard HTTP REST requests:
| Method | Route | Description |
|---|---|---|
| GET | /api/status |
Gets server state, MediaMTX state, network interface IP, and current camera metrics (resolution, live FPS, status). |
| GET | /api/interfaces |
Retrieves a list of active network adapter interfaces (IPs and card descriptions). |
| GET | /api/config |
Retrieves the current config.json configuration file payload. |
| GET | /api/logs |
Gets the 200 most recent lines of console logs. |
| POST | /api/control/start |
Starts the MediaMTX process and triggers Pylon grab loops for all cameras. |
| POST | /api/control/stop |
Stops all camera loops and terminates the MediaMTX process cleanly. |
| POST | /api/control/restart-camera?ip={ip} |
Restarts a single specified camera capture pipeline. |
| POST | /api/config |
Overwrites and updates config.json configurations (requires AppConfig JSON body payload). |
- Status stuck in "Reconnecting" or Logs showing "controlled by another application (0xE1018006)":
- The camera is locked by another program (e.g., Basler pylon Viewer). Close other software; the application will auto-reconnect successfully in 5 seconds.
- Video player stuck at "Loading / Spinning":
- A previous instance of
mediamtx.exeorffmpeg.exewas forced to terminate but remained as a zombie process. Execute the following in PowerShell/Command Prompt to clear them:Then restart the C# application.taskkill /F /IM mediamtx.exe; taskkill /F /IM ffmpeg.exe
- A previous instance of
- Green lines, pixel corruption, or frequent complete grab warnings:
- Insufficient network bandwidth or network adapter collisions. Refer to the Hardware Optimization section to enable Jumbo Frames, and increase Inter-Packet Delay (e.g.,
3000to4500) in configuration to smooth traffic flow.
- Insufficient network bandwidth or network adapter collisions. Refer to the Hardware Optimization section to enable Jumbo Frames, and increase Inter-Packet Delay (e.g.,