IPCamAppBar/IPCamLib/Concrete/RTSPStreamCamera.cs

114 lines
3.9 KiB
C#

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using FastBitmapLib;
using IPCamLib.FFMPEG;
using RtspClientSharp;
using RtspClientSharp.RawFrames.Video;
namespace IPCamLib.Concrete
{
/// <summary>
/// Implements the ICamera interface for IP cameras exposing an RTSP stream.
/// </summary>
public class RTSPStreamCamera : ICamera
{
private readonly Uri streamUri;
private readonly Dictionary<FFmpegVideoCodecId, FFmpegVideoDecoder> videoDecodersMap = new();
private readonly Bitmap bitmap;
private readonly TransformParameters transformParameters;
/// <param name="streamUri">The URI to the camera stream.
/// Can include basic credentials in the standard 'username:password@' format.</param>
/// <param name="width">The width of the viewport</param>
/// <param name="height">The height of the viewport</param>
public RTSPStreamCamera(Uri streamUri, int width, int height)
{
this.streamUri = streamUri;
transformParameters = new TransformParameters(RectangleF.Empty, new Size(width, height),
ScalingPolicy.Stretch, PixelFormat.Bgra32, ScalingQuality.FastBilinear);
bitmap = new Bitmap(width, height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
using var fastBitmap = bitmap.FastLock();
fastBitmap.Clear(Color.Black);
}
/// <inheritdoc />
public async Task Fetch(ICameraObserver observer, CancellationToken cancellationToken)
{
NetworkCredential credentials = null;
if (!string.IsNullOrEmpty(streamUri.UserInfo))
{
var parts = streamUri.UserInfo.Split(':');
credentials = new NetworkCredential(parts[0], parts.Length > 1 ? parts[1] : "");
}
else
credentials = new NetworkCredential(null, (string)null);
var connectionParameters = new ConnectionParameters(streamUri, credentials)
{
RtpTransport = RtpTransportProtocol.TCP
};
using var rtspClient = new RtspClient(connectionParameters);
rtspClient.FrameReceived += (_, rawFrame) =>
{
if (rawFrame is not RawVideoFrame rawVideoFrame)
return;
var decoder = GetDecoderForFrame(rawVideoFrame);
var decodedFrame = decoder.TryDecode(rawVideoFrame);
if (decodedFrame == null)
return;
using (var fastBitmap = bitmap.FastLock())
{
decodedFrame.TransformTo(fastBitmap.Scan0, fastBitmap.StrideInBytes, transformParameters);
}
// TODO await
observer.OnFrame(bitmap);
};
await rtspClient.ConnectAsync(cancellationToken);
await rtspClient.ReceiveAsync(cancellationToken);
}
private FFmpegVideoDecoder GetDecoderForFrame(RawVideoFrame videoFrame)
{
var codecId = DetectCodecId(videoFrame);
if (videoDecodersMap.TryGetValue(codecId, out var decoder))
return decoder;
decoder = FFmpegVideoDecoder.CreateDecoder(codecId);
videoDecodersMap.Add(codecId, decoder);
return decoder;
}
private static FFmpegVideoCodecId DetectCodecId(RawVideoFrame videoFrame)
{
return videoFrame switch
{
RawJpegFrame => FFmpegVideoCodecId.MJPEG,
RawH264Frame => FFmpegVideoCodecId.H264,
_ => throw new ArgumentOutOfRangeException(nameof(videoFrame))
};
}
}
}