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 { /// /// Implements the ICamera interface for IP cameras exposing an RTSP stream. /// public class RTSPStreamCamera : ICamera { private readonly Uri streamUri; private readonly Dictionary videoDecodersMap = new(); private readonly Bitmap bitmap; private readonly TransformParameters transformParameters; /// The URI to the camera stream. /// Can include basic credentials in the standard 'username:password@' format. /// The width of the viewport /// The height of the viewport 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); } /// 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)) }; } } }