注意点
1. DESCRIBE 第1次发送时返回401错误,返回信息带 realm,nonce,参数使用MD5校验后重新发送;
2. SETUP 的url信息,由DESCRIBE返回的head中的sdp字符串中解析,readonly,x-dimensions(视频分辨率),control(视频url),rtpmap(编解码信息)
3.PLAY的Session信息,由SETUP返回的head中的Session
4.解析完后,tcpclient接收rtp数据包,可用ffmepg进行解码
核心代码RTSPClient.cs
using System.Net.Sockets; using System.Text;namespace RtspClientCore {class RtspClient{string rtspUrl = "rtsp://192.168.0.2:554/h264/ch1/main/av_stream";string username = "admin";string password = "thzn123456";TcpClient tcpClient;NetworkStream tcpStream;private int cseq;public Uri rtspUri { get; set; }public int NewCSeq{get{return ++cseq;}}string Authorization = "";string Session = "";string UserAgent = "C# RTSP Client";public RtspClient(string rtspUrl, string username, string password){this.rtspUrl = rtspUrl;this.username = username;this.password = password;this.rtspUri = new Uri(rtspUrl);this.tcpClient = new TcpClient(rtspUri.Host, rtspUri.Port);this.tcpStream = tcpClient.GetStream();}RTSPResponse ExcuteRequest(string method, string url, string request, bool skip = false){string temp = request + "\r\n";temp = temp.Replace("@CSeq", NewCSeq.ToString());Logger.Info(temp);// SendRequestbyte[] requestBytes = Encoding.ASCII.GetBytes(temp);tcpStream.Write(requestBytes, 0, requestBytes.Length);// ReadResponse StreamReader reader = new StreamReader(tcpStream);RTSPResponse r1 = new RTSPResponse(reader, skip); switch (r1.StatusCode){ case "401":{//RTSP / 1.0 401 Unauthorized//CSeq: 1//WWW - Authenticate: Digest realm = "IP Camera(G7574)", nonce = "d355a9a6a081d0d5ce50d0dd90a14148", stale = "FALSE"//Date: Thu, Mar 16 2023 10:34:24 GMT string realm = string.Empty;string nonce = string.Empty;string authType = string.Empty;var auth = r1.Headers.Where(x => x.Key == "WWW-Authenticate").FirstOrDefault(); if (auth.Value.Contains("Digest")){// 摘要认证authType = "Digest";RtspUtil.GetDigestParams(auth.Value, ref realm, ref nonce); }else if (auth.Value.Contains("Basic")){// 基本认证authType = "Basic";// Authorization: Basic YWRtaW46YWRtaW4=\r\n\r\n}else{throw new Exception("Server auth mode not support:" + auth);}Authorization = RtspUtil.GetAuthorization(authType, url, username, password, realm, nonce, method);request += "Authorization:" + Authorization + "\r\n";r1 = ExcuteRequest(method, url, request, false);}break;}return r1;}public RTSPResponse DESCRIBE(){string mothed = "DESCRIBE";StringBuilder request = new StringBuilder();request.Append(mothed + " " + rtspUrl + " RTSP/1.0\r\n");request.Append("CSeq: @CSeq\r\n");request.Append("User-Agent: " + UserAgent + "\r\n");request.Append("Accept: application/sdp\r\n");return ExcuteRequest(mothed, rtspUrl, request.ToString()); }public RTSPResponse SETUP(string url){string mothed = "SETUP";StringBuilder request = new StringBuilder();request.Append(mothed + " " + url + " RTSP/1.0\r\n");request.Append("CSeq: @CSeq\r\n"); request.Append("Authorization: " + Authorization + "\r\n");request.Append("User-Agent: " + UserAgent + "\r\n");request.Append("Transport: RTP/AVP/TCP;unicast;interleaved=0-1\r\n");return ExcuteRequest(mothed, rtspUrl, request.ToString()); }public RTSPResponse PLAY(string url){string mothed = "PLAY";StringBuilder request = new StringBuilder();request.Append(mothed + " " + url + " RTSP/1.0\r\n");request.Append("CSeq: @CSeq\r\n");request.Append("Authorization: " + Authorization + "\r\n");request.Append("User-Agent: " + UserAgent + "\r\n");request.Append("Session: " + this.Session + "\r\n");request.Append("Range: npt=0.000-\r\n");return ExcuteRequest(mothed, rtspUrl, request.ToString(), true);}public RTSPResponse TEARDOWN(string url, string session){string mothed = "TEARDOWN";StringBuilder request = new StringBuilder();request.Append(mothed + " " + url + " RTSP/1.0\r\n");request.Append("CSeq: @CSeq\r\n");request.Append("Authorization: " + Authorization + "\r\n");request.Append("User-Agent: " + UserAgent + "\r\n");request.Append("Session: " + this.Session + "\r\n"); return ExcuteRequest(mothed, rtspUrl, request.ToString(), true);} public void Start(){RTSPResponse r1 = DESCRIBE();if ("200" == r1.StatusCode){SDP sdp_data = new SDP(r1.Response);bool find = false; bool video1 = false; bool video2 = false; var dimensions = string.Empty; var control = string.Empty; var rtpmap = string.Empty;for (int x = 0; x < sdp_data.MediaDescribes.Count; x++){var items = sdp_data.MediaDescribes[x].a;foreach (var item in items){string mediainfo = item.ToLower();if (mediainfo.Contains("recvonly")){video1 = true;}if (mediainfo.Contains("x-dimensions")){video2 = true;dimensions = item;}if (mediainfo.Contains("control")){control = item.Replace("control:", "");}if (mediainfo.Contains("rtpmap")){rtpmap = item.Replace("rtpmap:", "");} if (video1 && video2 & !string.IsNullOrEmpty(control) & !string.IsNullOrEmpty(rtpmap)){find = true;break;}}if (find){break;}}RTSPResponse r2 = SETUP(control);if ("200" == r2.StatusCode){ string sessionVal = r2.Headers.Where(x => x.Key == "Session").FirstOrDefault().Value;if (!string.IsNullOrEmpty(sessionVal)){string[] sessionParms = sessionVal.Split(';');if (sessionParms.Length > 1){this.Session = sessionParms[0];}else{this.Session = sessionVal;}RTSPResponse r3 = PLAY(control);if ("200" == r3.StatusCode){// 接收rtp数据byte[] buffer = new byte[1024];while (true){int bytesRead = tcpStream.Read(buffer, 0, buffer.Length);if (bytesRead == 0){break;}// 处理接收到的RTP数据//Console.WriteLine($"Received {bytesRead} bytes of RTP data.");}}}}}}} }