114 lines
3.9 KiB
C#
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))
|
|||
|
};
|
|||
|
}
|
|||
|
}
|
|||
|
}
|