轉載於:http://blog.csdn.net/zhouyongku/article/details/41546789
海康相機RTSP連接代碼分析
最近在做海康相機rtsp連接獲取音視頻的工作,現在介紹一下分析過程和源碼。
【源碼在我上傳的共享資料中:http://download.csdn.net/detail/zhouyongku/8203521】
一、基本原理
RTSP客戶端去連接服務器的最基本步驟如下:
(1)分析url中的端口號,創建一個與服務器[S-PORT]端口號的TCP連接用於RTSP命令交互 (2)執行RTSP命令請求,告訴服務器可以連接到本地的端口[C-PORT]進行數據發送 (3)客戶端從本地[C-PORT]讀取數據

二、研究步驟
來研究一下海康相機的RTSP數據是怎么交互的
(1)打開Wireshark,選擇"抓包"->"網絡接口"->選擇網卡->“開始”,在"過濾“欄輸入"rtsp",屏蔽不相關信息。
(2)打開VLC播放器,輸入海康相機RTSP地址
例如:【rtsp://admin:12345@192.168.1.145:554/MPEG-4/ch2/main/av_stream】
(3)播放幾秒后再點擊停止播放,退出VLC播放器。 (4)分析Wireshark數據

三、RTSP協議過程分析
1、RTSP協議梗概
就這幾個簡單的交互命令就能實現RTSP對接,C代表Client S代表Server
例如:C1-客戶端發的第一個命令 S1-服務器響應的第一個回復
【C1】OPTIONS rtsp://192.168.1.145:554/MPEG-4/ch2/main/av_stream RTSP/1.0RTSP
【S1】Reply: RTSP/1.0 200 OKRTSP 【C2】DESCRIBE rtsp://192.168.1.145:554/MPEG-4/ch2/main/av_stream RTSP/1.0RTSP 【S2】Reply: RTSP/1.0 401 Unauthorized RTSP 【C3】DESCRIBE rtsp://192.168.1.145:554/MPEG-4/ch2/main/av_stream RTSP/1.0RTSP 【S3】Reply: RTSP/1.0 200 OK, with session descriptionRTSP/SDP 【C4】SETUP rtsp://192.168.1.145:554/MPEG-4/ch2/main/av_stream/trackID=1 RTSP/1.0RTSP 【S4】Reply: RTSP/1.0 200 OK RTSP 【C5】SETUP rtsp://192.168.1.145:554/MPEG-4/ch2/main/av_stream/trackID=2 RTSP/1.0RTSP 【S5】Reply: RTSP/1.0 200 OK RTSP 【C6】PLAY rtsp://192.168.1.145:554/MPEG-4/ch2/main/av_stream/ RTSP/1.0RTSP 【S6】Reply: RTSP/1.0 200 OK RTSP 【C7】GET_PARAMETER rtsp://192.168.1.145:554/MPEG-4/ch2/main/av_stream/ RTSP/1.0RTSP 【S7】Reply: RTSP/1.0 200 OK RTSP 【C8】TEARDOWN rtsp://192.168.1.145:554/MPEG-4/ch2/main/av_stream/ RTSP/1.0RTSP 【S8】Reply: RTSP/1.0 200 OK RTSP
2、請求OPTION
【C1-OPTION】
Request: OPTIONS rtsp://192.168.1.145:554/MPEG-4/ch2/main/av_stream RTSP/1.0\r\nCSeq: 2\r\nUser-Agent: LibVLC/2.1.3 (LIVE555 Streaming Media v2014.01.21)\r\n\r\n詢問RTSP服務器有哪些命令可以使用
【S1-REPLY】
Response: RTSP/1.0 200 OK\r\nCSeq: 2\r\nPublic: OPTIONS, DESCRIBE, PLAY, PAUSE, SETUP, TEARDOWN, SET_PARAMETER, GET_PARAMETER\r\nDate: Thu, Nov 27 2014 11:59:41 GMT\r\n
服務器回應客戶端的請求。這個回應第一行為OK,則表明服務器接受查詢命令,並且反饋給客戶端信息,可以使用的命令有:OPTIONS, DESCRIBE, PLAY, PAUSE, SETUP, TEARDOWN, SET_PARAMETER, GET_PARAMETER,則下一步就可以向服務器發送這些命令,如果沒有,則服務器不接收這些命令。
3、無驗證請求資源描述DESCRIBE
【C2-DESCRIBE】
Request: DESCRIBE rtsp://192.168.1.145:554/MPEG-4/ch2/main/av_stream RTSP/1.0\r\nCSeq: 3\r\nUser-Agent: LibVLC/2.1.3 (LIVE555 Streaming Media v2014.01.21)\r\nAccept: application/sdp\r\n
\r\n
請求RTSP服務器描述自身有哪些音視頻資源【S2-REPLY】
Response: RTSP/1.0 401 Unauthorized\r\n CSeq: 3\r\n WWW-Authenticate: Digest realm="4419b727ab09", nonce="66bb9f0bf5ac93a909ac8e88877ae727", stale="FALSE"\r\n WWW-Authenticate: Basic realm="4419b727ab09"\r\nDate: Thu, Nov 27 2014 11:59:41 GMT\r\n
服務器回應客戶端的請求。第一行為Unauthorized,表明認證不成功服務器拒絕給出擁有的資源,因此我們需要再次發送認證的請求到服務器,則服務器才能給出擁有的資源
4、認證請求資源描述DESCRIBE
【C3-DESCRIBE】
Request: DESCRIBE rtsp://192.168.1.145:554/MPEG-4/ch2/main/av_stream RTSP/1.0\r\nCSeq: 4\r\nAuthorization: Digest username="admin", realm="4419b727ab09", nonce="66bb9f0bf5ac93a909ac8e88877ae727", uri="rtsp://192.168.1.145:554/MPEG-4/ch2/main/av_stream", response="108084646408d21aa255664781c886fc"\r\nUser-Agent: LibVLC/2.1.3 (LIVE555 Streaming Media v2014.01.21)\r\nAccept: application/sdp\r\n\r\n發送具有認證標識的信息到服務器,去獲取服務器的資源列表。怎樣的格式算正確認證的信息?這個關鍵在於response字段。[S2-REPLY]中反饋了兩個信息realm="4419b727ab09", nonce="66bb9f0bf5ac93a909ac8e88877ae727",**************************************response區域的計算規則如下***********************************
(1)當password為MD5編碼,則response = md5(<password>:<nonce>:md5(<cmd>:<url>));(2)當password為ANSI字符串,則response = md5(md5(<username>:<realm>:<password>):<nonce>:md5(<cmd>:<uri>));***********************************************************************************************************
其中 password=密碼,nonce=服務器返回的nonce,cmd=當前命令(DESCRIBE、SETUP等),realm=服務器返回的reaml,uri=請求的uri地址.當服務器接收到response為108084646408d21aa255664781c886fc的時候,服務器也會用這一套公式去計算,當發現計算出的值與客戶端提供的值吻合的時候,則表明用戶名和密碼校驗成功!
- void RtspRequest::SendRegisterRequest(string requestType)
- {
- string requestCmd;
- char cseq[256];
- char session[256];
- char author[500] = { 0 };
- char acc[200] = { 0 };
- char szInput[200] = { "" };
- char szOutput[200] = { 0 };
- char szMd5Pwd[200] = { 0 };
- char szmd5methorduri[200] = { 0 };
- m_CSeq++;
- if (m_SetupName.length())
- {
- requestCmd = requestType;
- requestCmd += " ";
- requestCmd += m_RequestsMrl;
- requestCmd += "/";
- requestCmd += m_SetupName;
- requestCmd += " RTSP/1.0";
- m_SetupName = "";
- }
- else
- {
- requestCmd = requestType;
- requestCmd += " ";
- requestCmd += m_RequestsMrl;
- requestCmd += " RTSP/1.0";
- }
- //當password為ANSI字符串 response = md5(md5(<username>:<realm>:<password>):<nonce>:md5(<cmd>:<uri>));
- //md5(<username>:<realm>:<password>)=md5(admin:4419b727ab09:12345)=fa47d934c754db5ebebd9b42a4412073
- sprintf_s(szInput, 200, "%s:%s:%s", m_uername.c_str(), m_realm.c_str(), m_password.c_str());
- md5_hash(szInput, strlen(szInput), szMd5Pwd);
- //md5(<cmd>:<uri>)=md5(DESCRIBE:rtsp://192.168.1.145:554/MPEG-4/ch2/main/av_stream) =0b0a4e77e852876bdfdf6ecb72ba3900
- sprintf_s(szInput, 200, "DESCRIBE:%s", m_RequestsMrl.c_str());
- md5_hash(szInput, strlen(szInput), szmd5methorduri);
- //md5(md5(<username>:<realm>:<password>):<nonce>:md5(<cmd>:<uri>));
- //=md5(fa47d934c754db5ebebd9b42a4412073:66bb9f0bf5ac93a909ac8e88877ae727:0b0a4e77e852876bdfdf6ecb72ba3900)=108084646408d21aa255664781c886fc
- //關於這一條,http://www.3464.com/Tools/MD5/index.asp 網站給出的結論則不一致:98f90a583e5c436f83da956c9b00767b
void RtspRequest::SendRegisterRequest(string requestType) { string requestCmd; char cseq[256]; char session[256]; char author[500] = { 0 }; char acc[200] = { 0 }; char szInput[200] = { "" }; char szOutput[200] = { 0 }; char szMd5Pwd[200] = { 0 }; char szmd5methorduri[200] = { 0 }; m_CSeq++; if (m_SetupName.length()) { requestCmd = requestType; requestCmd += " "; requestCmd += m_RequestsMrl; requestCmd += "/"; requestCmd += m_SetupName; requestCmd += " RTSP/1.0"; m_SetupName = ""; } else { requestCmd = requestType; requestCmd += " "; requestCmd += m_RequestsMrl; requestCmd += " RTSP/1.0"; } //當password為ANSI字符串 response = md5(md5(<username>:<realm>:<password>):<nonce>:md5(<cmd>:<uri>)); //md5(<username>:<realm>:<password>)=md5(admin:4419b727ab09:12345)=fa47d934c754db5ebebd9b42a4412073 sprintf_s(szInput, 200, "%s:%s:%s", m_uername.c_str(), m_realm.c_str(), m_password.c_str()); md5_hash(szInput, strlen(szInput), szMd5Pwd); //md5(<cmd>:<uri>)=md5(DESCRIBE:rtsp://192.168.1.145:554/MPEG-4/ch2/main/av_stream) =0b0a4e77e852876bdfdf6ecb72ba3900 sprintf_s(szInput, 200, "DESCRIBE:%s", m_RequestsMrl.c_str()); md5_hash(szInput, strlen(szInput), szmd5methorduri); //md5(md5(<username>:<realm>:<password>):<nonce>:md5(<cmd>:<uri>)); //=md5(fa47d934c754db5ebebd9b42a4412073:66bb9f0bf5ac93a909ac8e88877ae727:0b0a4e77e852876bdfdf6ecb72ba3900)=108084646408d21aa255664781c886fc //關於這一條,http://www.3464.com/Tools/MD5/index.asp 網站給出的結論則不一致:98f90a583e5c436f83da956c9b00767b
- <span style="white-space:pre"> </span>//我的代碼給出的值與VLC和其它工具給出的值完全匹配
- sprintf_s(szInput, 200, "%s:%s:%s", szMd5Pwd, m_nonce.c_str(), szmd5methorduri);
- md5_hash(szInput, strlen(szInput), szOutput);
- m_response = szOutput;
- _snprintf(cseq, 256, "CSeq: %u", m_CSeq);
- _snprintf(author, 500, "Authorization: Digest username=\"%s\", realm=\"%s\", nonce=\"%s\", uri=\"%s\", response=\"%s\"",
- m_uername.c_str(), m_realm.c_str(), m_nonce.c_str(), m_RequestsMrl.c_str(), m_response.c_str());
- _snprintf(acc, 500, "Accept: application/sdp");
- if (requestType.compare("TEARDOWN") == 0)
- m_Session = 0;
- _snprintf(session, 256, "Session: %I64u", m_Session);
- Write(requestCmd.c_str());
- Write(cseq);
- Write(author);
- Write("User-Agent: LibVLC/2.1.3 (LIVE555 Streaming Media v2014.01.21)");
- Write(acc);
- if (m_Session > 0)
- Write(session);
- WriteFields();
- Write("");
- }
<span style="white-space:pre"> </span>//我的代碼給出的值與VLC和其它工具給出的值完全匹配 sprintf_s(szInput, 200, "%s:%s:%s", szMd5Pwd, m_nonce.c_str(), szmd5methorduri); md5_hash(szInput, strlen(szInput), szOutput); m_response = szOutput; _snprintf(cseq, 256, "CSeq: %u", m_CSeq); _snprintf(author, 500, "Authorization: Digest username=\"%s\", realm=\"%s\", nonce=\"%s\", uri=\"%s\", response=\"%s\"", m_uername.c_str(), m_realm.c_str(), m_nonce.c_str(), m_RequestsMrl.c_str(), m_response.c_str()); _snprintf(acc, 500, "Accept: application/sdp"); if (requestType.compare("TEARDOWN") == 0) m_Session = 0; _snprintf(session, 256, "Session: %I64u", m_Session); Write(requestCmd.c_str()); Write(cseq); Write(author); Write("User-Agent: LibVLC/2.1.3 (LIVE555 Streaming Media v2014.01.21)"); Write(acc); if (m_Session > 0) Write(session); WriteFields(); Write(""); }
【S3-REPLY】
Response: RTSP/1.0 200 OK\r\nCSeq: 4\r\nContent-type: application/sdpContent-Base: rtsp://192.168.1.145:554/MPEG-4/ch2/main/av_stream/\r\nContent-length: 672\r\nSession Description ProtocolSession Description Protocol Version (v): 0Owner/Creator, Session Id (o): - 1417089581269778 1417089581269778 IN IP4 192.168.1.145Session Name (s): Media PresentationE-mail Address (e): NONEBandwidth Information (b): AS:5100Time Description, active time (t): 0 0Session Attribute (a): control:rtsp://192.168.1.145:554/MPEG-4/ch2/main/av_stream/Media Description, name and address (m): video 0 RTP/AVP 96Bandwidth Information (b): AS:5000Media Attribute (a): control:rtsp://192.168.1.145:554/MPEG-4/ch2/main/av_stream/trackID=1Media Attribute (a): rtpmap:96 H264/90000Media Attribute (a): fmtp:96 profile-level-id=420029; packetization-mode=1; sprop-parameter-sets=Z0KAH4iLUCgC3QgAADhAAAr8gCA=,aM44gA==Media Description, name and address (m): audio 0 RTP/AVP 0Bandwidth Information (b): AS:50Media Attribute (a): control:rtsp://192.168.1.145:554/MPEG-4/ch2/main/av_stream/trackID=2Media Attribute (a): rtpmap:0 PCMU/8000Media Attribute (a): Media_header:MEDIAINFO=494D4B48010100000400010010710110401F000000FA000000000000000000000000000000000000;Media Attribute (a): appversion:1.0首先第一行給了OK,表明上一個命令成功--DESCRIBE成功得到服務器認證其次,給出了服務器的媒體信息-具有兩路碼流--音頻(audio)和視頻(video) 並給出了請求兩路數據的地址
5、請求視頻資源
【C4-SETUP】
Request: SETUP rtsp://192.168.1.145:554/MPEG-4/ch2/main/av_stream/trackID=1 RTSP/1.0\r\nCSeq: 5\r\nAuthorization: Digest username="admin", realm="4419b727ab09", nonce="66bb9f0bf5ac93a909ac8e88877ae727", uri="rtsp://192.168.1.145:554/MPEG-4/ch2/main/av_stream/", response="dfd7cfc85819d15d622e4491ad12217d"\r\nUser-Agent: LibVLC/2.1.3 (LIVE555 Streaming Media v2014.01.21)\r\nTransport: RTP/AVP;unicast;client_port=56732-56733\r\n
請求獲取流媒體服務器上的視頻資源 並告知服務器可以往客戶端56732端口發送視頻數據,56733為RTCP接收端口,發送方式為單播。傳輸方式為UDP**********************傳輸方式與Transport string的對應關系**********************
UDP傳輸:Transport:RTP/AVP
TCP傳輸:Transport:RTP/AVP/TCPRAW UDP傳輸:Transport:RAW/RAW/UDP
****************************************************************************************
【S4-REPLY】
Response: RTSP/1.0 200 OK\r\nCSeq: 5\r\nSession: 1350856257;timeout=60Transport: RTP/AVP;unicast;client_port=56732-56733;server_port=8208-8209;ssrc=4a3a67d5;mode="play"Date: Thu, Nov 27 2014 11:59:41 GMT\r\n\r\n
服務器同意發送視頻數據給客戶端 56732為視頻接收端,56733為RTCP接收端口,發送方式為單播。傳輸方式為UDP
6、請求音頻資源
【C5-SETUP】
Request: SETUP rtsp://192.168.1.145:554/MPEG-4/ch2/main/av_stream/trackID=2 RTSP/1.0\r\nCSeq: 6\r\nAuthorization: Digest username="admin", realm="4419b727ab09", nonce="66bb9f0bf5ac93a909ac8e88877ae727", uri="rtsp://192.168.1.145:554/MPEG-4/ch2/main/av_stream/", response="dfd7cfc85819d15d622e4491ad12217d"\r\nUser-Agent: LibVLC/2.1.3 (LIVE555 Streaming Media v2014.01.21)\r\nTransport: RTP/AVP;unicast;client_port=56734-56735Session: 1350856257\r\n
請求獲取服務器上的音頻資源 並告知服務器可以往客戶端56734端口發送視頻數據,56735為RTCP接收端口,發送方式為單播。傳輸方式為UDP
【S5-REPLY】
Response: RTSP/1.0 200 OK\r\nCSeq: 6\r\nSession: 1350856257;timeout=60Transport: RTP/AVP;unicast;client_port=56734-56735;server_port=8226-8227;ssrc=232306b5;mode="play"Date: Thu, Nov 27 2014 11:59:41 GMT\r\n\r\n
服務器同意發送音頻數據給客戶端 56734為視頻接收端,56735為RTCP接收端口,發送方式為單播。傳輸方式為UDP
7、開始數據接收
【C6-PLAY】
Request: PLAY rtsp://192.168.1.145:554/MPEG-4/ch2/main/av_stream/ RTSP/1.0\r\nCSeq: 7\r\nAuthorization: Digest username="admin", realm="4419b727ab09", nonce="66bb9f0bf5ac93a909ac8e88877ae727", uri="rtsp://192.168.1.145:554/MPEG-4/ch2/main/av_stream/", response="4c4e89a5894d2b208489fde1aeaf9e44"\r\nUser-Agent: LibVLC/2.1.3 (LIVE555 Streaming Media v2014.01.21)\r\nSession: 1350856257Range: npt=0.000-\r\n\r\n
告知服務器一切准備就緒,數據可以發過來了!come on baby!
【S6-REPLY】
Response: RTSP/1.0 200 OK\r\nCSeq: 7\r\nSession: 1350856257RTP-Info: url=rtsp://192.168.1.145:554/MPEG-4/ch2/main/av_stream/trackID=1;seq=31283;rtptime=1712470431,url=rtsp://192.168.1.145:554/MPEG-4/ch2/main/av_stream/trackID=2;seq=3370;rtptime=1846536290\r\nDate: Thu, Nov 27 2014 11:59:41 GMT\r\n\r\n
服務器告知客戶端,朋友我已經送數據過來了,你可以接收了!這個時候我們打開Wireshark,在過濾欄輸入rtp,可以看到如下數據,表明服務器已經向客戶端的56732、56734端口發送數據
RTP拆包到圖像顯示和音頻播放,會在我的下一篇博客中詳細解釋
8、關閉播放
【C7-TEARDOWN】
Request: TEARDOWN rtsp://192.168.1.145:554/MPEG-4/ch2/main/av_stream/ RTSP/1.0\r\nMethod: TEARDOWNURL: rtsp://192.168.1.145:554/MPEG-4/ch2/main/av_stream/CSeq: 9\r\nAuthorization: Digest username="admin", realm="4419b727ab09", nonce="66bb9f0bf5ac93a909ac8e88877ae727", uri="rtsp://192.168.1.145:554/MPEG-4/ch2/main/av_stream/", response="af15346442151a51f1478da1858778d5"\r\nUser-Agent: LibVLC/2.1.3 (LIVE555 Streaming Media v2014.01.21)\r\nSession: 1350856257\r\n
關閉數據傳輸 釋放服務器資源 如果需要重新獲取碼流 則需要從1~6再來一次
【S7-REPLY】
Response: RTSP/1.0 200 OK\r\nStatus: 200CSeq: 9\r\nSession: 1350856257Date: Thu, Nov 27 2014 11:59:42 GMT\r\n\r\n
服務器已經成功關閉當前鏈接
本文引用:http://blog.163.com/seek_for/blog/static/1116353920116154386537/