RTSP(Real Time Streaming Protocol)是由Real Network和Netscape共同提出的如何有效地在IP網絡上傳輸流媒體數據的應用層協議。RTSP對流媒體提供了諸如暫停,快進等控制,而它自己並不傳輸數據,RTSP的做用至關於流媒體服務器的遠程控制。服務器端能夠自行選擇使用TCP或UDP來傳送串流內容,它的語法和運做跟HTTP 1.1相似,但並不特別強調時間同步,因此比較能容忍網絡延遲。並且容許同時多個串流需求控制(Multicast),除了能夠下降服務器端的網絡用量,還能夠支持多方視頻會議(Video onference)。 由於與HTTP1.1的運做方式類似,因此代理服務器《Proxy》的快取功能《Cache》也一樣適用於RTSP,並因RTSP具備從新導向功能,可視實際負載狀況來轉換提供服務的服務器,以免過大的負載集中於同一服務器而形成延遲。
rtsp和http的區別和聯繫
(1)聯繫:二者都用純文原本發送消息,且rtsp協議的語法也和HTTP相似。Rtsp一開始這樣設計,也是爲了可以兼容使用之前寫的HTTP協議分析代碼 。
(2)區別:rtsp是有狀態的,不一樣的是RTSP的命令須要知道如今正處於一個什麼狀態,也就是說rtsp的命令老是按照順序來發送,某個命令總在另一個命令以前要發送。Rtsp無論處於什麼狀態都不會去斷掉鏈接。,而http則不保存狀態,協議在發送一個命令之後,鏈接就會斷開,且命令之間是沒有依賴性的。rtsp協議使用554端口,http使用80端口。
rtsp和sip的區別和聯繫
SIP(Session Initiation Protocol),是基於IP的一個應用層控制協議。因爲SIP是基於純文本的信令協議,能夠管理不一樣接入網絡上的會話等。會話能夠是終端設備之間任何類型的通訊,如視頻會話、既時信息處理或協做會話。該協議不會定義或限制可以使用的業務,傳輸、服務質量、計費、安全性等問題都由基本核心網絡和其它協議處理。
(1)聯繫:sip和rtsp都是應用層的控制協議,負責一次通訊過程的創建和控制和結束,不負責中間的傳輸部分。他們都是基於純文本的信令協議,穿牆性能良好。支持tcp、udp,支持多方通訊。他們都須要服務器支持,都支持會話中重定向。sip和rtsp 都使用sdp協議來傳送媒體參數,使用rtp(rtcp)協議來傳輸媒體流。
(2)區別:rtsp是專門爲流媒體制定的協議,在多個媒體流的時間同步方面比sip強大。rtsp還提供網絡負載均衡的功能,減輕服務器壓力和網絡帶寬要求。sip通常用來建立一次音頻、視頻通話(雙向),而rtsp通常用來作視頻點播、視頻監控等(單向)。固然,從原理上講,rtsp也能夠作雙向的視頻通話。
rtsp負責創建和控制會話,rtp負責多媒體的傳輸,rtcp配合rtp作控制和流量統計,他們是合做的關係。
RTSP的消息
RTSP的消息有兩大類,一是請求消息(request),一是迴應消息(response),兩種消息的格式不一樣。
請求消息格式:
方法 URI RTSP版本 CR LF
消息頭 CR LF CR LF
消息體 CR LF
其中方法包括OPTIONS、SETUP、PLAY、TEARDOWN等待,URI是接收方(服務端)的地址,例如:rtsp://192.168.22.136:5000/v0,每行後面的CR LF表示回車換行,須要接收端有相應的解析,最後一個消息頭須要有兩個CR LF。java
迴應消息格式:
RTSP版本 狀態碼 解釋 CR LF
消息頭 CR LF CR LF
消息體 CR LF
其中RTSP版本通常都是RTSP/1.0,狀態碼是一個數值,200表示成功,解釋是與狀態碼對應的文本解釋。數組
狀態碼由三位數組成,表示方法執行的結果,定義以下:安全
1XX:保留,未來使用;服務器
2XX:成功,操做被接收、理解、接受(received,understand,accepted);網絡
3XX:重定向,要完成操做必須進行進一步操做;session
4XX:客戶端出錯,請求有語法錯誤或沒法實現;app
5XX:服務器出錯,服務器沒法實現合法的請求。負載均衡
RTSP的方法
rtsp中定義的方法有:OPTIONS, DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE, SCALE, GET_PARAMETER ,SET_PARAMETER
1.OPTION
目的是獲得服務器提供的可用方法:
OPTIONS rtsp://192.168.20.136:5000/xxx666 RTSP/1.0
CSeq: 1 //每一個消息都有序號來標記,第一個包一般是option請求消息
User-Agent: VLC media player (LIVE555 Streaming Media v2005.11.10)
服務器的迴應信息包括提供的一些方法,例如:
RTSP/1.0 200 OK
Server: UServer 0.9.7_rc1
Cseq: 1 //每一個迴應消息的cseq數值和請求消息的cseq相對應
Public: OPTIONS, DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE, SCALE, GET_PARAMETER //服務器提供的可用的方法
2.DESCRIBE
C向S發起DESCRIBE請求,爲了獲得會話描述信息(SDP):
DESCRIBE rtsp://192.168.20.136:5000/xxx666 RTSP/1.0
CSeq: 2
token:
Accept: application/sdp
User-Agent: VLC media player (LIVE555 Streaming Media v2005.11.10)
服務器迴應一些對此會話的描述信息(sdp):
RTSP/1.0 200 OK
Server: UServer 0.9.7_rc1
Cseq: 2
x-prev-url: rtsp://192.168.20.136:5000
x-next-url: rtsp://192.168.20.136:5000
x-Accept-Retransmit: our-retransmit
x-Accept-Dynamic-Rate: 1
Cache-Control: must-revalidate
Last-Modified: Fri, 10 Nov 2006 12:34:38 GMT
Date: Fri, 10 Nov 2006 12:34:38 GMT
Expires: Fri, 10 Nov 2006 12:34:38 GMT
Content-Base: rtsp://192.168.20.136:5000/xxx666/
Content-Length: 344
Content-Type: application/sdp
v=0 //如下都是sdp信息
o=OnewaveUServerNG 1451516402 1025358037 IN IP4 192.168.20.136
s=/xxx666
u=http:///
e=admin@
c=IN IP4 0.0.0.0
t=0 0
a=isma-compliance:1,1.0,1
a=range:npt=0-
m=video 0 RTP/AVP 96 //m表示媒體描述,下面是對會話中視頻通道的媒體描述
a=rtpmap:96 MP4V-ES/90000
a=fmtp:96 profile-level-id=245;config=000001B0F5000001B509000001000000012000C888B0E0E0FA62D089028307
a=control:trackID=0//trackID=0表示視頻流用的是通道0
3.SETUP
客戶端提醒服務器創建會話,並肯定傳輸模式:
SETUP rtsp://192.168.20.136:5000/xxx666/trackID=0 RTSP/1.0
CSeq: 3
Transport: RTP/AVP/TCP;unicast;interleaved=0-1
User-Agent: VLC media player (LIVE555 Streaming Media v2005.11.10)
//uri中帶有trackID=0,表示對該通道進行設置。Transport參數設置了傳輸模式,包的結構。接下來的數據包頭部第二個字節位置就是interleaved,它的值是每一個通道都不一樣的,trackID=0的interleaved值有兩個0或1,0表示rtp包,1表示rtcp包,接受端根據interleaved的值來區別是哪一種數據包。
服務器迴應信息:
RTSP/1.0 200 OK
Server: UServer 0.9.7_rc1
Cseq: 3
Session: 6310936469860791894 //服務器迴應的會話標識符
Cache-Control: no-cache
Transport: RTP/AVP/TCP;unicast;interleaved=0-1;ssrc=6B8B4567
4.PLAY
客戶端發送播放請求:
PLAY rtsp://192.168.20.136:5000/xxx666 RTSP/1.0
CSeq: 4
Session: 6310936469860791894
Range: npt=0.000- //設置播放時間的範圍
User-Agent: VLC media player (LIVE555 Streaming Media v2005.11.10)
服務器迴應信息:
RTSP/1.0 200 OK
Server: UServer 0.9.7_rc1
Cseq: 4
Session: 6310936469860791894
Range: npt=0.000000-
RTP-Info: url=trackID=0;seq=17040;rtptime=1467265309
//seq和rtptime都是rtp包中的信息
5.TEARDOWN
客戶端發起關閉請求:
TEARDOWN rtsp://192.168.20.136:5000/xxx666 RTSP/1.0
CSeq: 5
Session: 6310936469860791894
User-Agent: VLC media player (LIVE555 Streaming Media v2005.11.10)
服務器迴應:
RTSP/1.0 200 OK
Server: UServer 0.9.7_rc1
Cseq: 5
Session: 6310936469860791894
Connection: Close
以上方法都是交互過程當中最爲經常使用的,其它還有一些重要的方法如get/set_parameter,pause,redirect等等
ps:
sdp的格式
v=<version>
o=<username> <session id> <version> <network type> <address type> <address>
s=<session name>
i=<session description>
u=<URI>
e=<email address>
p=<phone number>
c=<network type> <address type> <connection address>
b=<modifier>:<bandwidth-value>
t=<start time> <stop time>
r=<repeat interval> <active duration> <list of offsets from start-time>
z=<adjustment time> <offset> <adjustment time> <offset> ....
k=<method>
k=<method>:<encryption key>
a=<attribute>
a=<attribute>:<value>
m=<media> <port> <transport> <fmt list>
v = (協議版本)
o = (全部者/建立者和會話標識符)
s = (會話名稱)
i = * (會話信息)
u = * (URI 描述)
e = * (Email 地址)
p = * (電話號碼)
c = * (鏈接信息)
b = * (帶寬信息)
z = * (時間區域調整)
k = * (加密密鑰)
a = * (0 個或多個會話屬性行)
時間描述:
t = (會話活動時間)
r = * (0或屢次重複次數)
媒體描述:
m = (媒體名稱和傳輸地址)
i = * (媒體標題)
c = * (鏈接信息 — 若是包含在會話層則該字段可選)
b = * (帶寬信息)
k = * (加密密鑰)
a = * (0 個或多個媒體屬性行)
RTSP客戶端的JAVA實現
3.1 接口IEvent.java socket
接口IEvent.java的代碼以下:tcp
- package com.amigo.rtsp;
-
- import java.io.IOException;
- import java.nio.channels.SelectionKey;
-
- public interface IEvent {
-
- void connect(SelectionKey key) throws IOException;
-
-
- void read(SelectionKey key) throws IOException;
-
-
- void write() throws IOException;
-
-
- void error(Exception e);
- }
3.2 RTSP的測試類:RTSPClient.java
RTSP的測試類RTSPClient.java類的代碼以下所示:
- package com.amigo.rtsp;
-
- import java.io.IOException;
- import java.net.InetSocketAddress;
- import java.nio.ByteBuffer;
- import java.nio.channels.SelectionKey;
- import java.nio.channels.Selector;
- import java.nio.channels.SocketChannel;
- import java.util.Iterator;
- import java.util.concurrent.atomic.AtomicBoolean;
-
- public class RTSPClient extends Thread implements IEvent {
-
- private static final String VERSION = " RTSP/1.0/r/n";
- private static final String RTSP_OK = "RTSP/1.0 200 OK";
-
-
- private final InetSocketAddress remoteAddress;
-
-
- private final InetSocketAddress localAddress;
-
-
- private SocketChannel socketChannel;
-
-
- private final ByteBuffer sendBuf;
-
-
- private final ByteBuffer receiveBuf;
-
- private static final int BUFFER_SIZE = 8192;
-
-
- private Selector selector;
-
- private String address;
-
- private Status sysStatus;
-
- private String sessionid;
-
-
- private AtomicBoolean shutdown;
-
- private int seq=1;
-
- private boolean isSended;
-
- private String trackInfo;
-
-
- private enum Status {
- init, options, describe, setup, play, pause, teardown
- }
-
- public RTSPClient(InetSocketAddress remoteAddress,
- InetSocketAddress localAddress, String address) {
- this.remoteAddress = remoteAddress;
- this.localAddress = localAddress;
- this.address = address;
-
-
- sendBuf = ByteBuffer.allocateDirect(BUFFER_SIZE);
- receiveBuf = ByteBuffer.allocateDirect(BUFFER_SIZE);
- if (selector == null) {
-
- try {
- selector = Selector.open();
- } catch (final IOException e) {
- e.printStackTrace();
- }
- }
-
- startup();
- sysStatus = Status.init;
- shutdown=new AtomicBoolean(false);
- isSended=false;
- }
-
- public void startup() {
- try {
-
- socketChannel = SocketChannel.open();
-
- socketChannel.socket().setSoTimeout(30000);
- socketChannel.configureBlocking(false);
- socketChannel.socket().bind(localAddress);
- if (socketChannel.connect(remoteAddress)) {
- System.out.println("開始創建鏈接:" + remoteAddress);
- }
- socketChannel.register(selector, SelectionKey.OP_CONNECT
- | SelectionKey.OP_READ | SelectionKey.OP_WRITE, this);
- System.out.println("端口打開成功");
-
- } catch (final IOException e1) {
- e1.printStackTrace();
- }
- }
-
- public void send(byte[] out) {
- if (out == null || out.length < 1) {
- return;
- }
- synchronized (sendBuf) {
- sendBuf.clear();
- sendBuf.put(out);
- sendBuf.flip();
- }
-
-
- try {
- write();
- isSended=true;
- } catch (final IOException e) {
- e.printStackTrace();
- }
- }
-
- public void write() throws IOException {
- if (isConnected()) {
- try {
- socketChannel.write(sendBuf);
- } catch (final IOException e) {
- }
- } else {
- System.out.println("通道爲空或者沒有鏈接上");
- }
- }
-
- public byte[] recieve() {
- if (isConnected()) {
- try {
- int len = 0;
- int readBytes = 0;
-
- synchronized (receiveBuf) {
- receiveBuf.clear();
- try {
- while ((len = socketChannel.read(receiveBuf)) > 0) {
- readBytes += len;
- }
- } finally {
- receiveBuf.flip();
- }
- if (readBytes > 0) {
- final byte[] tmp = new byte[readBytes];
- receiveBuf.get(tmp);
- return tmp;
- } else {
- System.out.println("接收到數據爲空,從新啓動鏈接");
- return null;
- }
- }
- } catch (final IOException e) {
- System.out.println("接收消息錯誤:");
- }
- } else {
- System.out.println("端口沒有鏈接");
- }
- return null;
- }
-
- public boolean isConnected() {
- return socketChannel != null && socketChannel.isConnected();
- }
-
- private void select() {
- int n = 0;
- try {
- if (selector == null) {
- return;
- }
- n = selector.select(1000);
-
- } catch (final Exception e) {
- e.printStackTrace();
- }
-
-
- if (n > 0) {
- for (final Iterator<SelectionKey> i = selector.selectedKeys()
- .iterator(); i.hasNext();) {
-
- final SelectionKey sk = i.next();
- i.remove();
-
- if (!sk.isValid()) {
- continue;
- }
-
-
- final IEvent handler = (IEvent) sk.attachment();
- try {
- if (sk.isConnectable()) {
- handler.connect(sk);
- } else if (sk.isReadable()) {
- handler.read(sk);
- } else {
-
- }
- } catch (final Exception e) {
- handler.error(e);
- sk.cancel();
- }
- }
- }
- }
-
- public void shutdown() {
- if (isConnected()) {
- try {
- socketChannel.close();
- System.out.println("端口關閉成功");
- } catch (final IOException e) {
- System.out.println("端口關閉錯誤:");
- } finally {
- socketChannel = null;
- }
- } else {
- System.out.println("通道爲空或者沒有鏈接");
- }
- }
-
- @Override
- public void run() {
-
- while (!shutdown.get()) {
- try {
- if (isConnected()&&(!isSended)) {
- switch (sysStatus) {
- case init:
- doOption();
- break;
- case options:
- doDescribe();
- break;
- case describe:
- doSetup();
- break;
- case setup:
- if(sessionid==null&&sessionid.length()>0){
- System.out.println("setup尚未正常返回");
- }else{
- doPlay();
- }
- break;
- case play:
- doPause();
- break;
-
- case pause:
- doTeardown();
- break;
- default:
- break;
- }
- }
-
- select();
- try {
- Thread.sleep(1000);
- } catch (final Exception e) {
- }
- } catch (final Exception e) {
- e.printStackTrace();
- }
- }
-
- shutdown();
- }
-
- public void connect(SelectionKey key) throws IOException {
- if (isConnected()) {
- return;
- }
-
- socketChannel.finishConnect();
- while (!socketChannel.isConnected()) {
- try {
- Thread.sleep(300);
- } catch (final InterruptedException e) {
- e.printStackTrace();
- }
- socketChannel.finishConnect();
- }
-
- }
-
- public void error(Exception e) {
- e.printStackTrace();
- }
-
- public void read(SelectionKey key) throws IOException {
-
- final byte[] msg = recieve();
- if (msg != null) {
- handle(msg);
- } else {
- key.cancel();
- }
- }
-
- private void handle(byte[] msg) {
- String tmp = new String(msg);
- System.out.println("返回內容:");
- System.out.println(tmp);
- if (tmp.startsWith(RTSP_OK)) {
- switch (sysStatus) {
- case init:
- sysStatus = Status.options;
- break;
- case options:
- sysStatus = Status.describe;
- trackInfo=tmp.substring(tmp.indexOf("trackID"));
- break;
- case describe:
- sessionid = tmp.substring(tmp.indexOf("Session: ") + 9, tmp
- .indexOf("Date:"));
- if(sessionid!=null&&sessionid.length()>0){
- sysStatus = Status.setup;
- }
- break;
- case setup:
- sysStatus = Status.play;
- break;
- case play:
- sysStatus = Status.pause;
- break;
- case pause:
- sysStatus = Status.teardown;
- shutdown.set(true);
- break;
- case teardown:
- sysStatus = Status.init;
- break;
- default:
- break;
- }
- isSended=false;
- } else {
- System.out.println("返回錯誤:" + tmp);
- }
-
- }
-
- private void doTeardown() {
- StringBuilder sb = new StringBuilder();
- sb.append("TEARDOWN ");
- sb.append(this.address);
- sb.append("/");
- sb.append(VERSION);
- sb.append("Cseq: ");
- sb.append(seq++);
- sb.append("/r/n");
- sb.append("User-Agent: RealMedia Player HelixDNAClient/10.0.0.11279 (win32)/r/n");
- sb.append("Session: ");
- sb.append(sessionid);
- sb.append("/r/n");
- send(sb.toString().getBytes());
- System.out.println(sb.toString());
- }
-
- private void doPlay() {
- StringBuilder sb = new StringBuilder();
- sb.append("PLAY ");
- sb.append(this.address);
- sb.append(VERSION);
- sb.append("Session: ");
- sb.append(sessionid);
- sb.append("Cseq: ");
- sb.append(seq++);
- sb.append("/r/n");
- sb.append("/r/n");
- System.out.println(sb.toString());
- send(sb.toString().getBytes());
-
- }
-
- private void doSetup() {
- StringBuilder sb = new StringBuilder();
- sb.append("SETUP ");
- sb.append(this.address);
- sb.append("/");
- sb.append(trackInfo);
- sb.append(VERSION);
- sb.append("Cseq: ");
- sb.append(seq++);
- sb.append("/r/n");
- sb.append("Transport: RTP/AVP;UNICAST;client_port=16264-16265;mode=play/r/n");
- sb.append("/r/n");
- System.out.println(sb.toString());
- send(sb.toString().getBytes());
- }
-
- private void doOption() {
- StringBuilder sb = new StringBuilder();
- sb.append("OPTIONS ");
- sb.append(this.address.substring(0, address.lastIndexOf("/")));
- sb.append(VERSION);
- sb.append("Cseq: ");
- sb.append(seq++);
- sb.append("/r/n");
- sb.append("/r/n");
- System.out.println(sb.toString());
- send(sb.toString().getBytes());
- }
-
- private void doDescribe() {
- StringBuilder sb = new StringBuilder();
- sb.append("DESCRIBE ");
- sb.append(this.address);
- sb.append(VERSION);
- sb.append("Cseq: ");
- sb.append(seq++);
- sb.append("/r/n");
- sb.append("/r/n");
- System.out.println(sb.toString());
- send(sb.toString().getBytes());
- }
-
- private void doPause() {
- StringBuilder sb = new StringBuilder();
- sb.append("PAUSE ");
- sb.append(this.address);
- sb.append("/");
- sb.append(VERSION);
- sb.append("Cseq: ");
- sb.append(seq++);
- sb.append("/r/n");
- sb.append("Session: ");
- sb.append(sessionid);
- sb.append("/r/n");
- send(sb.toString().getBytes());
- System.out.println(sb.toString());
- }
-
- public static void main(String[] args) {
- try {
-
-
- RTSPClient client = new RTSPClient(
- new InetSocketAddress("218.207.101.236", 554),
- new InetSocketAddress("192.168.2.28", 0),
- "rtsp://218.207.101.236:554/mobile/3/67A451E937422331/8jH5QPU5GWS07Ugn.sdp");
- client.start();
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
其中:rtsp://218.207.101.236:554/mobile/3/67A451E937422331/8jH5QPU5GWS07Ugn.sdp爲我在網上找到的一個rtsp的sdp地址,讀者可自行更換,RTSP的默認端口爲554.
3.3 運行結果
運行RTSPClient.java,運行結果以下所示:
- 端口打開成功
- OPTIONS rtsp:
- Cseq: 1
-
-
- 返回內容:
- RTSP/1.0 200 OK
- Server: PVSS/1.4.8 (Build/20090111; Platform/Win32; Release/StarValley; )
- Cseq: 1
- Public: DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE, OPTIONS, ANNOUNCE, RECORD
-
-
- DESCRIBE rtsp:
- Cseq: 2
-
-
- 返回內容:
- RTSP/1.0 200 OK
- Server: PVSS/1.4.8 (Build/20090111; Platform/Win32; Release/StarValley; )
- Cseq: 2
- Content-length: 421
- Date: Mon, 03 Aug 2009 08:50:36 GMT
- Expires: Mon, 03 Aug 2009 08:50:36 GMT
- Content-Type: application/sdp
- x-Accept-Retransmit: our-retransmit
- x-Accept-Dynamic-Rate: 1
- Content-Base: rtsp:
-
- v=0
- o=MediaBox 127992 137813 IN IP4 0.0.0.0
- s=RTSP Session
- i=Starv Box Live Cast
- c=IN IP4 218.207.101.236
- t=0 0
- a=range:npt=now-
- a=control:*
- m=video 0 RTP/AVP 96
- b=AS:20
- a=rtpmap:96 MP4V-ES/1000
- a=fmtp:96 profile-level-id=8; config=000001b008000001b5090000010000000120008440fa282c2090a31f; decode_buf=12586
- a=range:npt=now-
- a=framerate:5
- a=framesize:96 176-144
- a=cliprect:0,0,144,176
- a=control:trackID=1
-
- SETUP rtsp:
- RTSP/1.0
- Cseq: 3
- Transport: RTP/AVP;UNICAST;client_port=16264-16265;mode=play
-
-
- 返回內容:
- RTSP/1.0 200 OK
- Server: PVSS/1.4.8 (Build/20090111; Platform/Win32; Release/StarValley; )
- Cseq: 3
- Session: 15470472221769
- Date: Mon, 03 Aug 2009 08:50:36 GMT
- Expires: Mon, 03 Aug 2009 08:50:36 GMT
- Transport: RTP/AVP;UNICAST;mode=play;client_port=16264-16265;server_port=20080-20081
-
-
- PLAY rtsp:
- Session: 15470472221769
- Cseq: 4
-
-
- 返回內容:
- RTSP/1.0 200 OK
- Server: PVSS/1.4.8 (Build/20090111; Platform/Win32; Release/StarValley; )
- Cseq: 4
- Session: 15470472221769
- RTP-Info: url=rtsp:
-
-
- PAUSE rtsp:
- Cseq: 5
- Session: 15470472221769
-
-
- 返回內容:
- RTSP/1.0 200 OK
- Server: PVSS/1.4.8 (Build/20090111; Platform/Win32; Release/StarValley; )
- Cseq: 5
- Session: 15470472221769
-
-
- TEARDOWN rtsp:
- Cseq: 6
- User-Agent: RealMedia Player HelixDNAClient/10.0.0.11279 (win32)
- Session: 15470472221769
-
-
- 返回內容:
- RTSP/1.0 200 OK
- Server: PVSS/1.4.8 (Build/20090111; Platform/Win32; Release/StarValley; )
- Cseq: 6
- Session: 15470472221769
- Connection: Close
-
-
- 端口關閉成功
對照運行結果,讀者能夠熟悉RTSP的經常使用命令.