BaluMediaServer.CameraStreamer 1.0.0

There is a newer version of this package available.
See the version list below for details.
dotnet add package BaluMediaServer.CameraStreamer --version 1.0.0
                    
NuGet\Install-Package BaluMediaServer.CameraStreamer -Version 1.0.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="BaluMediaServer.CameraStreamer" Version="1.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="BaluMediaServer.CameraStreamer" Version="1.0.0" />
                    
Directory.Packages.props
<PackageReference Include="BaluMediaServer.CameraStreamer" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add BaluMediaServer.CameraStreamer --version 1.0.0
                    
#r "nuget: BaluMediaServer.CameraStreamer, 1.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package BaluMediaServer.CameraStreamer@1.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=BaluMediaServer.CameraStreamer&version=1.0.0
                    
Install as a Cake Addin
#tool nuget:?package=BaluMediaServer.CameraStreamer&version=1.0.0
                    
Install as a Cake Tool

📡 Balu Media Server - MAUI RTSP Server for Android

MIT License .NET Android Platform

A powerful, lightweight, and easy-to-integrate RTSP server library for .NET MAUI on Android. Stream live camera feeds with MJPEG and H.264 codecs, featuring both RTSP and HTTP streaming capabilities.

🚀 Project Motivation

This project was born from a real need: I wanted to run an RTSP server on a custom Android device. However, since I'm not a fan of Java/Kotlin and currently focusing on C# for my specialization, I chose to develop this using .NET MAUI, a C# cross-platform framework that I love.

I quickly discovered a lack of existing libraries for RTSP streaming on Android in the C# ecosystem—especially for mobile devices. So I decided to mix both worlds: use Kotlin for low-level Android camera access, and C# for everything else.

🎯 Purpose and Vision

The aim is to offer a simple, easily integrable, and lightweight RTSP server for Android using MAUI. It supports raw camera frame capture and streaming over RTSP using MJPEG and H.264 codecs. I hope this helps other developers avoid the struggles I faced, and have a better, cleaner entry point into mobile RTSP streaming using MAUI and C#.

MIT licensed. Free for everyone. No strings attached.

📦 What's Inside?

🔹 Kotlin AAR Module

  • Handles low-level camera access using Android's native APIs
  • Designed to allow streaming from front, back, or both cameras
  • Delivers frames in YUV_420 format via two callbacks
  • Can work without a display (headless mode)

🔹 MAUI Integration

  • Uses a .NET MAUI Library to integrate with the AAR
  • Provides two camera services: FrontCameraService and BackCameraService
  • Real-time frame capture at resolutions from 640x480 to 1920x1080+ (device dependent)
  • Default frame rate: 45 FPS (adjusts dynamically)

🔹 RTSP Server (Pure C#)

  • Full RTSP Protocol Compliance: Follows RTSP, RTP, and RTCP specifications
  • Dual Codec Support:
    • MJPEG: Works smoothly, high bandwidth, no compression
    • H.264: Hardware-accelerated encoding, optimized for MediaTek devices
  • High Concurrency: Can handle at least 12 simultaneous clients (tested)
  • Authentication: Digest authentication included for basic security
  • Transport Modes: UDP and TCP interleaved support
  • Dynamic Bitrate: Automatic adjustment based on network conditions
  • Multiple Profiles: Support for /live/front and /live/back routes

🔹 MJPEG HTTP Server

  • Simple, independent MJPEG server for easy HTML display
  • Allows usage of <img src="http://your-device:port/mjpeg" /> in web pages
  • Built to avoid duplicate frame processing—shares frames with the RTSP stream
  • Dual camera support with separate endpoints

🔹 Utility Features

  • Start/stop MJPEG or camera services via EventBus
  • Snapshot capability using callbacks
  • Built-in foreground service for background compatibility
  • Simple demo project included
  • Callbacks available to monitor connected clients, stream status, etc.
  • Basic watchdog system to disconnect inactive clients
  • Automatic resource cleanup and memory management

🛠️ Installation

Prerequisites

  • .NET 8.0 or later
  • Android SDK API Level 26+ (Android 8.0+)
  • Visual Studio 2022 with MAUI workload
  • Android device or emulator

NuGet Package (Coming Soon)

<PackageReference Include="BaluMediaServer.CameraStreamer" Version="1.0.0" />

Manual Installation

  1. Clone this repository
  2. Add the project reference to your MAUI application
  3. Add required permissions to your AndroidManifest.xml

📋 Required Permissions

Add these permissions to your Platforms/Android/AndroidManifest.xml:

<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />

🚀 Quick Start

Basic RTSP Server Setup

using BaluMediaServer.Services;
using BaluMediaServer.Models;

public class MainPage : ContentPage
{
    private Server _rtspServer;

    public MainPage()
    {
        InitializeComponent();
        
        // Request camera permissions
        RequestPermissions();
        
        // Initialize RTSP server with required authentication
        _rtspServer = new Server(
            Port: 7778,                    // RTSP port
            MaxClients: 12,                // Maximum concurrent clients
            Address: "0.0.0.0",           // Bind address
            Users: new Dictionary<string, string> 
            { 
                { "admin", "password123" } // Authentication is required
            }
        );
        
        // Start the server
        bool started = _rtspServer.Start();
        
        if (started)
        {
            Console.WriteLine("RTSP Server started successfully!");
            Console.WriteLine($"Front camera: rtsp://your-ip:7778/live/front");
            Console.WriteLine($"Back camera: rtsp://your-ip:7778/live/back");
        }
    }

    private async void RequestPermissions()
    {
        await Permissions.RequestAsync<Permissions.Camera>();
    }

    protected override void OnDisappearing()
    {
        _rtspServer?.Stop();
        base.OnDisappearing();
    }
}

MJPEG HTTP Server Setup

using BaluMediaServer.Services;
using BaluMediaServer.Repositories;

public class StreamingPage : ContentPage
{
    private MjpegServer _mjpegServer;

    public StreamingPage()
    {
        InitializeComponent();
        
        // Initialize MJPEG server
        _mjpegServer = new MjpegServer(port: 8089);
        
        // Start MJPEG streaming
        _mjpegServer.Start();
        
        // Or use EventBus for decoupled control
        EventBuss.SendCommand(BussCommand.START_MJPEG_SERVER);
    }

    protected override void OnDisappearing()
    {
        _mjpegServer?.Stop();
        EventBuss.SendCommand(BussCommand.STOP_MJPEG_SERVER);
        base.OnDisappearing();
    }
}

Using with .NET MAUI

using BaluMediaServer.Services;
using BaluMediaServer.Repositories;
using BaluMediaServer.Models;

public partial class MainPage : ContentPage
{
    private Server _rtspServer;
    private MjpegServer _mjpegServer;
    private bool _isStreaming = false;
    private int _clientCount = 0;

    public MainPage()
    {
        InitializeComponent();
        InitializeServers();
    }

    private async void InitializeServers()
    {
        // Request camera permissions
        await Permissions.RequestAsync<Permissions.Camera>();
        
        // Setup authentication (required)
        var users = new Dictionary<string, string>
        {
            { "admin", "password123" },
            { "viewer", "readonly" }
        };
        
        // Initialize servers
        _rtspServer = new Server(
            Port: 7778,
            MaxClients: 12,
            Address: "0.0.0.0",
            Users: users  // Authentication is required
        );
        
        _mjpegServer = new MjpegServer(port: 8089);
        
        // Subscribe to events
        Server.OnStreaming += OnStreamingStateChanged;
        Server.OnClientsChange += OnClientsChanged;
        
        // Start RTSP server
        bool started = _rtspServer.Start();
        
        if (started)
        {
            var localIP = GetLocalIPAddress();
            DisplayAlert("Server Started", 
                $"RTSP Server running at:\n" +
                $"rtsp://{localIP}:7778/live/back\n" +
                $"rtsp://{localIP}:7778/live/front", "OK");
        }
    }

    private void OnStreamingStateChanged(object? sender, bool isStreaming)
    {
        _isStreaming = isStreaming;
        MainThread.BeginInvokeOnMainThread(() => {
            // Update UI
            StatusLabel.Text = isStreaming ? "🔴 Live" : "⚫ Offline";
        });
    }

    private void OnClientsChanged(List<Client> clients)
    {
        _clientCount = clients.Count(c => c.Socket?.Connected ?? false);
        MainThread.BeginInvokeOnMainThread(() => {
            ClientCountLabel.Text = $"{_clientCount} client(s) connected";
        });
    }

    private void OnStartStreamingClicked(object sender, EventArgs e)
    {
        _mjpegServer.Start();
        EventBuss.SendCommand(BussCommand.START_CAMERA_BACK);
        EventBuss.SendCommand(BussCommand.START_CAMERA_FRONT);
    }

    private void OnStopStreamingClicked(object sender, EventArgs e)
    {
        _mjpegServer.Stop();
        EventBuss.SendCommand(BussCommand.STOP_CAMERA_BACK);
        EventBuss.SendCommand(BussCommand.STOP_CAMERA_FRONT);
    }

    protected override void OnDisappearing()
    {
        _rtspServer?.Stop();
        _mjpegServer?.Stop();
        base.OnDisappearing();
    }
}

📖 Detailed API Documentation

Server Class

The main RTSP server implementation that handles client connections and streaming.

Constructor
public Server(
    int Port = 7778,                           // RTSP server port
    int MaxClients = 100,                      // Maximum concurrent clients
    string Address = "0.0.0.0",               // Bind address
    Dictionary<string, string> Users           // Authentication users (required)
)
Methods
// Start the RTSP server
public bool Start()

// Stop the server and cleanup resources
public void Stop()

// Static method to encode YUV data to JPEG
public static byte[] EncodeToJpeg(byte[] rawImageData, int width, int height, Android.Graphics.ImageFormatType format)
Events
// Fired when streaming state changes
public static event EventHandler<bool>? OnStreaming;

// Fired when client list changes
public static event Action<List<Client>>? OnClientsChange;

// Fired when new frame is available from back camera (for general purpose use: snapshots, processing, etc.)
public static event EventHandler<FrameEventArgs>? OnNewBackFrame;

// Fired when new frame is available from front camera (for general purpose use: snapshots, processing, etc.)
public static event EventHandler<FrameEventArgs>? OnNewFrontFrame;

MjpegServer Class

HTTP server for MJPEG streaming, perfect for web browser integration.

Constructor
public MjpegServer(int port = 8089)
Methods
// Start the MJPEG HTTP server
public void Start()

// Stop the server
public void Stop()

// Push a frame to all connected clients
public void PushFrame(byte[] jpegBytes, bool front = false)
Endpoints
  • http://your-ip:port/Back/ - Back camera stream
  • http://your-ip:port/Front/ - Front camera stream

Camera Services

Low-level camera access services for frame capture.

BackCameraService / FrontCameraService
// Start camera capture
public void StartCapture(int width = 640, int height = 480)

// Stop camera capture
public void StopCapture()

// Event fired when new frame is available
public event EventHandler<FrameEventArgs>? FrameReceived;

// Event fired when error occurs
public event EventHandler<string>? ErrorOccurred;

EventBus System

Decoupled communication system for controlling services.

// Available commands
public enum BussCommand
{
    START_CAMERA_FRONT,
    STOP_CAMERA_FRONT,
    START_CAMERA_BACK,
    STOP_CAMERA_BACK,
    START_MJPEG_SERVER,
    STOP_MJPEG_SERVER
}

// Send command
EventBuss.SendCommand(BussCommand.START_CAMERA_BACK);

// Subscribe to commands
EventBuss.Command += (command) => {
    // Handle command
};

🔧 Advanced Configuration

Authentication (Required)

Authentication is mandatory for the RTSP server. You must provide user credentials when initializing the server.

// Required: Provide authentication users
var users = new Dictionary<string, string>
{
    { "admin", "secure_password" },
    { "viewer", "readonly_pass" },
    { "mobile", "mobile123" }
};

var server = new Server(
    Port: 7778,
    Users: users  // This parameter is required
);

Note: The server uses Digest authentication by default for security. Basic authentication is also supported for compatibility.

H.264 Encoder Configuration

The H.264 encoder automatically optimizes for MediaTek devices but can be configured:

// The encoder is automatically configured when streaming starts
// Default settings:
// - Bitrate: 2,000,000 bps (2 Mbps)
// - Frame rate: 25 FPS
// - Profile: Baseline
// - Keyframe interval: 2 seconds

// Dynamic bitrate adjustment happens automatically based on network conditions

Multiple Camera Resolutions

// Start cameras with custom resolution
var frontCamera = new FrontCameraService();
frontCamera.StartCapture(1920, 1080); // Full HD

var backCamera = new BackCameraService();
backCamera.StartCapture(1280, 720);   // HD

Frame Capture for Snapshots and Processing

The server provides general-purpose frame events that can be used for snapshots, image processing, or custom applications. These events work independently of the streaming functionality.

// Subscribe to frame events for custom processing
Server.OnNewBackFrame += (sender, frameArgs) => {
    // frameArgs.Data contains raw YUV data
    // frameArgs.Width, frameArgs.Height contain dimensions
    // frameArgs.Timestamp contains capture timestamp
    
    // Convert to JPEG for snapshot
    var jpegData = Server.EncodeToJpeg(
        frameArgs.Data, 
        frameArgs.Width, 
        frameArgs.Height, 
        Android.Graphics.ImageFormatType.Nv21
    );
    
    // Save snapshot
    await SaveSnapshotAsync(jpegData);
    
    // Or perform custom image processing
    ProcessFrame(frameArgs.Data, frameArgs.Width, frameArgs.Height);
};

Server.OnNewFrontFrame += (sender, frameArgs) => {
    // Same processing for front camera frames
    HandleFrontCameraFrame(frameArgs);
};

// Manually trigger camera capture for snapshots using EventBus
private async Task TakeSnapshot()
{
    // Start camera temporarily if not already running
    EventBuss.SendCommand(BussCommand.START_CAMERA_BACK);
    
    // Wait for frame capture
    await Task.Delay(500);
    
    // Stop camera if not needed for streaming
    if (!IsCurrentlyStreaming())
    {
        EventBuss.SendCommand(BussCommand.STOP_CAMERA_BACK);
    }
}

// Example: Automatic snapshot every 30 seconds
private async void StartPeriodicSnapshots()
{
    while (true)
    {
        await TakeSnapshot();
        await Task.Delay(TimeSpan.FromSeconds(30));
    }
}

🌐 Network Usage

RTSP URLs

  • Back Camera (H.264): rtsp://your-ip:7778/live/back
  • Front Camera (H.264): rtsp://your-ip:7778/live/front
  • Back Camera (MJPEG): rtsp://your-ip:7778/live/back/mjpeg
  • Front Camera (MJPEG): rtsp://your-ip:7778/live/front/mjpeg

HTTP MJPEG URLs

  • Back Camera: http://your-ip:8089/Back/
  • Front Camera: http://your-ip:8090/Front/
VLC Media Player
  1. Open VLC
  2. Go to Media → Open Network Stream
  3. Enter: rtsp://admin:password123@your-ip:7778/live/back
  4. Click Play
FFmpeg
# View stream
ffmpeg -i rtsp://admin:password123@your-ip:7778/live/back -f sdl output

# Record stream
ffmpeg -i rtsp://admin:password123@your-ip:7778/live/back -c copy output.mp4

# Re-stream to another server
ffmpeg -i rtsp://admin:password123@your-ip:7778/live/back -c copy -f rtsp rtsp://other-server/stream
OBS Studio
  1. Add Source → Media Source
  2. Uncheck "Local File"
  3. Input: rtsp://admin:password123@your-ip:7778/live/back
  4. Click OK
Web Browser (MJPEG only)
<img src="http://your-ip:8089/Back/" alt="Live Stream" />

🔍 Troubleshooting

Common Issues

Camera Permission Denied
// Always request permissions before starting
var status = await Permissions.RequestAsync<Permissions.Camera>();
if (status != PermissionStatus.Granted)
{
    // Handle permission denied
    await DisplayAlert("Error", "Camera permission is required", "OK");
    return;
}
Port Already in Use
try 
{
    var server = new Server(Port: 7778);
    bool started = server.Start();
    if (!started)
    {
        // Try alternative port
        server = new Server(Port: 7779);
        started = server.Start();
    }
}
catch (Exception ex)
{
    Console.WriteLine($"Server start failed: {ex.Message}");
}
Network Connectivity Issues
// Check network connectivity
var networkAccess = Connectivity.Current.NetworkAccess;
if (networkAccess != NetworkAccess.Internet)
{
    await DisplayAlert("Error", "No network connection", "OK");
    return;
}

// Get local IP address for clients to connect
var localIP = server.GetLocalIpAddress();
Console.WriteLine($"Connect to: rtsp://{localIP}:7778/live/back");
H.264 Encoding Issues
// Check if device supports hardware encoding
try 
{
    var encoder = new MediaTekH264Encoder(640, 480);
    bool started = encoder.Start();
    if (!started)
    {
        // Fallback to MJPEG
        Console.WriteLine("H.264 not supported, using MJPEG");
    }
}
catch (Exception ex)
{
    Console.WriteLine($"H.264 encoder error: {ex.Message}");
}
Performance Optimization
Memory Management
// Properly dispose of resources
protected override void OnDisappearing()
{
    _rtspServer?.Stop();           // Stops all services
    _mjpegServer?.Stop();          // Stops HTTP server
    _frontCamera?.StopCapture();   // Stops camera capture
    _backCamera?.StopCapture();    // Stops camera capture
    
    base.OnDisappearing();
}
Reduce Latency & Improve Reliability
// TCP transport is recommended over UDP for reliability
// The client (VLC, FFmpeg, etc.) will automatically negotiate transport
// To force TCP in VLC: go to Tools > Preferences > Input/Codecs > Network > RTP over RTSP (TCP)

// For FFmpeg, use TCP explicitly:
// ffmpeg -rtsp_transport tcp -i rtsp://admin:password@your-ip:7778/live/back output.mp4

// Reduce frame rate for better performance
frontCamera.StartCapture(640, 480); // Lower resolution = better performance

// Monitor client count and adjust quality
Server.OnClientsChange += (clients) => {
    if (clients.Count > 5)
    {
        // Reduce quality for multiple clients
        EventBuss.SendCommand(BussCommand.STOP_CAMERA_FRONT);
    }
};
Transport Protocol Recommendations
// Current known issue: UDP transport may fail when switching between transport modes
// Workaround: Use TCP transport which is more reliable

// In VLC Media Player:
// 1. Go to Tools > Preferences
// 2. Show settings: All
// 3. Navigate to Input / Codecs > Network
// 4. Set "RTP over RTSP (TCP)" to "Always"

// In FFmpeg:
// Use the -rtsp_transport tcp flag

Images

Streaming back camera (ffplay)

Mobile App

Streaming front camera (ffplay)

Mobile App

Mobile App Interface

Mobile App

⚠️ Current Limitations

  • Platform Support: Only Android 8.0+ (API 26+) is currently supported
  • Framework Support: Currently tested only with .NET 9.0 (Partial support on .NET 8.0)
  • Configuration: Limited runtime configuration options for codec parameters
  • Documentation: No comprehensive API documentation yet (this README serves as primary docs)
  • H.264 Stability: H.264 codec may have stability issues on some non-MediaTek devices
  • Transport Protocol: UDP transport has known issues - TCP transport is recommended for reliable streaming
  • iOS Support: Not available (long-term roadmap item)
  • Image orientation: Some devices can experiment image rotation
  • Image blazor preview: Some devices can experiment some issues displaying the images fetched from the MJPEG Server, this can depend if the devices allow access to LoopBack or not (Not fixable at all)

🛣️ Roadmap

Short Term (v1.1)

  • ⬜ Fix H.264 stream stutter issues
  • ⬜ Add support for multiple profiles/routes (/live/front, /live/back)
  • ⬜ Add user/password control panel
  • ⬜ Fix image rotation
  • ⬜ Add bitrate/resolution configuration
  • Fix UDP transport reliability issues (currently TCP is recommended)

Medium Term (v1.2-1.3)

  • ⬜ Add H.265 (HEVC) codec support
  • ⬜ Reduce streaming latency (target: <100ms)
  • ⬜ Add comprehensive code documentation
  • ⬜ NuGet package distribution
  • ⬜ Add unit tests and integration tests

Long Term (v2.0+)

  • ⬜ iOS support via .NET MAUI
  • ⬜ Audio streaming support
  • ⬜ WebRTC integration
  • ⬜ Cloud streaming integration
  • ⬜ Advanced analytics and monitoring

📄 License

This project is licensed under the MIT License - see the LICENSE file for details.

MIT License Summary:

  • ✅ Commercial use
  • ✅ Modification
  • ✅ Distribution
  • ✅ Private use
  • ❌ Liability
  • ❌ Warranty

💡 Why This Matters

There are few (if any) options to integrate RTSP servers with Android using C# and MAUI. This project bridges that gap. While it's still in early stages, it already provides a clean way to stream camera feeds using modern C# tooling—and it's open for everyone to contribute, extend, or just use freely.

🙏 Acknowledgments

  • MediaTek for excellent hardware encoding support
  • .NET MAUI Team for the amazing cross-platform framework
  • Android Camera2 API for providing low-level camera access
  • RTSP/RTP Specifications for the streaming protocols
  • Open Source Community for inspiration and support

📬 Support & Contact

  • Issues: Please use GitHub Issues for bug reports and feature requests
  • Discussions: Use GitHub Discussions for general questions and ideas
  • Email: danielulrichtamayo@gmail.com

Getting Help

  1. Check the Documentation: This README covers most use cases
  2. Search Issues: Your question might already be answered
  3. Create an Issue: Provide detailed information about your problem
  4. Community: Join discussions with other developers

Thanks for checking out Balu Media Server!

Feel free to report bugs, suggest features, or fork and play around with the code.

Let's make mobile RTSP with C# a thing! 💪


Made with ❤️ and C# • Open Source • MIT Licensed

Product Compatible and additional computed target framework versions.
.NET net9.0-android35.0 is compatible.  net10.0-android was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.1.9 207 8/7/2025
1.1.5 219 8/5/2025
1.1.2 164 8/4/2025
1.0.0 68 8/3/2025

Initial release with dual camera support