用NetCore手撸RTSP交互协议

注意点
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); // SendRequest byte[] 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."); } } } } } } } }
qq:505645074
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律