-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathRtspControl.cs
163 lines (147 loc) · 6.81 KB
/
RtspControl.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Threading.Tasks;
using System.Threading;
using SimpleRtspPlayer.RawFramesDecoding.FFmpeg;
using SimpleRtspPlayer.RawFramesDecoding;
using RtspClientSharp;
using RtspClientSharp.RawFrames.Video;
using RtspClientSharp.Rtsp;
using Logging;
namespace RtspInfra {
public class RtspControl {
private readonly Dictionary<FFmpegVideoCodecId, FFmpegVideoDecoder> _videoDecodersMap = new Dictionary<FFmpegVideoCodecId, FFmpegVideoDecoder>();
private Bitmap _bmp;
private TransformParameters _transformParameters;
private CancellationTokenSource _cancellationTokenSource;
private Size _sensorSize;
private Rectangle _sensorRect;
public RtspControl() {
}
// RtspControl status
public enum Status {
UNDEFINED = -1,
RUNNING = 0,
CANCELLED = 1,
EXCEPTION = 2,
WAITCANCEL = 3,
NORESPOND = 4,
}
private Status _status = Status.UNDEFINED;
public Status GetStatus {
get {
return _status;
}
}
// error notification
public delegate void ErrorHandler();
public event ErrorHandler Error;
// connect to a RTSP stream
public async void StartRTSP(Size sensorSize, string url) {
_status = Status.UNDEFINED;
// camera sensor data
_sensorSize = sensorSize;
_sensorRect = new Rectangle(0, 0, sensorSize.Width, sensorSize.Height);
// connection data
var connectionParameters = new ConnectionParameters(new Uri(url));
if ( _cancellationTokenSource != null ) {
_cancellationTokenSource.Dispose();
_cancellationTokenSource = null;
}
_cancellationTokenSource = new CancellationTokenSource();
TimeSpan delay = TimeSpan.FromSeconds(10);
using ( var rtspClient = new RtspClient(connectionParameters) ) {
rtspClient.FrameReceived += RtspClient_FrameReceived;
while ( true ) {
try {
_status = Status.RUNNING;
await rtspClient.ConnectAsync(_cancellationTokenSource.Token);
await rtspClient.ReceiveAsync(_cancellationTokenSource.Token);
} catch ( OperationCanceledException ) {
Logger.logTextLn(DateTime.Now, "RtspControl cancelled");
_status = Status.CANCELLED;
return;
} catch ( RtspClientException e ) {
Logger.logTextLnU(DateTime.Now, "RtspControl exception #1: " + e.InnerException?.Message);
_status = Status.EXCEPTION;
if ( e.InnerException != null && e.InnerException.Message.Contains("did not properly respond") ) {
_status = Status.NORESPOND;
}
Error?.Invoke();
return;
} catch ( InvalidOperationException e ) {
Logger.logTextLnU(DateTime.Now, "RtspControl exception #2: " + e.Message);
_status = Status.EXCEPTION;
Error?.Invoke();
return;
}
}
}
}
// stop connection to a RTSP stream
public void StopRTSP() {
_status = Status.WAITCANCEL;
Logger.logTextLn(DateTime.Now, "RtspControl is about to cancel");
_cancellationTokenSource?.Cancel();
Logger.logTextLn(DateTime.Now, "RtspControl token cancellation is requested");
}
// tell a listener about a new frame; inspired by https://www.bytehide.com/blog/how-to-implement-events-in-csharp
public delegate void NewFrameHandler(Bitmap bmp);
public event NewFrameHandler NewFrame;
// how to get a frame from a RTSP stream
private void RtspClient_FrameReceived(object sender, RtspClientSharp.RawFrames.RawFrame e) {
// sanity checks
if ( _cancellationTokenSource == null || _cancellationTokenSource.IsCancellationRequested ) {
return;
}
if ( !(e is RawVideoFrame rawVideoFrame) ) {
return;
}
// select correct decoder
var codecId = DetectCodecId(rawVideoFrame);
if ( !_videoDecodersMap.TryGetValue(codecId, out FFmpegVideoDecoder decoder) ) {
decoder = FFmpegVideoDecoder.CreateDecoder(codecId);
_videoDecodersMap.Add(codecId, decoder);
}
var decodedVideoFrame = decoder.TryDecode(rawVideoFrame);
// convert decoded frame to bmp
if ( decodedVideoFrame != null ) {
if ( _bmp != null ) {
_bmp.Dispose();
}
// new and empty bmp in the size of the native camera sensor
_bmp = new Bitmap(_sensorSize.Width, _sensorSize.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
// lock the bitmap's bits
var bmpData = _bmp.LockBits(_sensorRect, System.Drawing.Imaging.ImageLockMode.ReadWrite, _bmp.PixelFormat);
// transform data
IntPtr ptr = bmpData.Scan0;
_transformParameters = new TransformParameters(
RectangleF.Empty,
_sensorSize,
ScalingPolicy.RespectAspectRatio,
PixelFormat.Bgra32, ScalingQuality.Nearest);
decodedVideoFrame.TransformTo(ptr, bmpData.Stride, _transformParameters);
try {
// unlock bitmap's bits
_bmp.UnlockBits(bmpData);
// raise event toward a listener
NewFrame?.Invoke(_bmp);
} catch ( System.InvalidOperationException ioe ) {
Logger.logTextLn(DateTime.Now, "RtspClient_FrameReceived ioe: " + ioe.Message);
} catch ( Exception ex ) {
Logger.logTextLn(DateTime.Now, "RtspClient_FrameReceived exception: " + ex.Message);
}
}
}
// need to distinguish between H264 and MJPEG
private FFmpegVideoCodecId DetectCodecId(RawVideoFrame videoFrame) {
if ( videoFrame is RawJpegFrame )
return FFmpegVideoCodecId.MJPEG;
if ( videoFrame is RawH264Frame )
return FFmpegVideoCodecId.H264;
Logger.logTextLn(DateTime.Now, "DetectCodecId unsupported: " + nameof(videoFrame));
throw new ArgumentOutOfRangeException(nameof(videoFrame));
}
}
}