转载请注明出处:http://www.blogjava.net/amigoxie/archive/2009/08/03/289620.html
一. 参考资料
1. 《RTSP简单命令》:http://blog.youkuaiyun.com/feidragon319/archive/2007/08/14/1742357.aspx
2. http://bbs.21eic.com/dispbbs.asp?boardid=15&Id=22948
3. 《RTSP客户端的Java实现》:http://hi.baidu.com/ssyuan/blog/item/566df6defac1dc5094ee37eb.html
二. RTSP的常用命令与解释
其中C是客户端,S是服务端。
2.1 OPTIONS
C->S: OPTION request //询问S有哪些方法可用
S->C: OPTION response //S回应信息中包括提供的所有可用方法
使用举例:
客户端到服务端:
OPTIONS rtsp:
//
218.207.101.236:554/mobile/3/67A451E937422331 RTSP/1.0
Cseq:
1
服务端对OPTIONS的回应:
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
2.2 DESCRIBE
C->S: DESCRIBE request //要求得到S提供的媒体初始化描述信息
S->C: DESCRIBE response //S回应媒体初始化描述信息,主要是sdp
使用举例:
客户端到服务端:
DESCRIBE rtsp:
//
218.207.101.236:554/mobile/3/67A451E937422331/8jH5QPU5GWS07Ugn.sdp RTSP/1.0
Cseq:
2
服务端对OPTIONS的回应:
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
:
21
:
33
GMT
Expires: Mon,
03
Aug
2009
08
:
21
:
33
GMT
Content
-
Type: application
/
sdp
x
-
Accept
-
Retransmit: our
-
retransmit
x
-
Accept
-
Dynamic
-
Rate:
1
Content
-
Base: rtsp:
//
218.207.101.236:554/mobile/3/67A451E937422331/8jH5QPU5GWS07Ugn.sdp/
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
2.3 SETUP
C->S: SETUP request //设置会话的属性,以及传输模式,提醒S建立会话
S->C: SETUP response //S建立会话,返回会话标识符,以及会话相关信息
客户端到服务端的请求举例:
SETUP rtsp:
//
218.207.101.236:554/mobile/3/67A451E937422331/8jH5QPU5GWS07Ugn.sdp/trackID=1
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:
26633092229589
Date: Mon,
03
Aug
2009
08
:
21
:
33
GMT
Expires: Mon,
03
Aug
2009
08
:
21
:
33
GMT
Transport: RTP
/
AVP;UNICAST;mode
=
play;client_port
=
16264
-
16265
;server_port
=
20026
-
20027
2.4 PLAY
C->S: PLAY request //C请求播放
S->C: PLAY response //S回应该请求的信息
客户端到服务端的请求举例:
PLAY rtsp:
//
218.207.101.236:554/mobile/3/67A451E937422331/8jH5QPU5GWS07Ugn.sdp RTSP/1.0
Session:
26633092229589
Cseq:
4
服务端对客户端的回应举例:
RTSP
/
1.0
200
OK
Server: PVSS
/
1.4
.
8
(Build
/
20090111
; Platform
/
Win32; Release
/
StarValley; )
Cseq:
4
Session:
26633092229589
RTP
-
Info: url
=
rtsp:
//
218.207.101.236:554/mobile/3/67A451E937422331/8jH5QPU5GWS07Ugn.sdp/trackID=1;seq=0;rtptime=0
2.5 PAUSE
C->S: PAUSE request //C请求暂停播放
S->C: PAUSE response //S回应该请求的信息
客户端到服务端的请求举例:
PAUSE rtsp:
//
218.207.101.236:554/mobile/3/67A451E937422331/8jH5QPU5GWS07Ugn.sdp/ RTSP/1.0
Cseq:
5
Session:
26633092229589
服务端对客户端的回应举例:
RTSP
/
1.0
200
OK
Server: PVSS
/
1.4
.
8
(Build
/
20090111
; Platform
/
Win32; Release
/
StarValley; )
Cseq:
5
Session:
26633092229589
2.6 TEARDOWN
C->S: TEARDOWN request //C请求关闭会话
S->C: TEARDOWN response //S回应该请求
客户端到服务端的请求举例:
TEARDOWN rtsp:
//
218.207.101.236:554/mobile/3/67A451E937422331/8jH5QPU5GWS07Ugn.sdp/ RTSP/1.0
Cseq:
6
User
-
Agent: RealMedia Player HelixDNAClient
/
10.0
.
0.11279
(win32)
Session:
26633092229589
服务端对客户端的回应举例:
RTSP
/
1.0
200
OK
Server: PVSS
/
1.4
.
8
(Build
/
20090111
; Platform
/
Win32; Release
/
StarValley; )
Cseq:
6
Session:
26633092229589
Connection: Close
三. RTSP客户端的Java实现
3.1 接口IEvent.java
接口IEvent.java的代码如下:
package
com.amigo.rtsp;

import
java.io.IOException;
import
java.nio.channels.SelectionKey;


/** */
/**
* IEvent.java 网络事件处理器,当Selector可以进行操作时,调用这个接口中的方法.
* 2007-3-22 下午03:35:51
* @author sycheng
* @version 1.0
*/

public
interface
IEvent
{

/** *//**
* 当channel得到connect事件时调用这个方法.
* @param key
* @throws IOException
*/
void connect(SelectionKey key) throws IOException;


/** *//**
* 当channel可读时调用这个方法.
* @param key
* @throws IOException
*/
void read(SelectionKey key) throws IOException;


/** *//**
* 当channel可写时调用这个方法.
* @throws IOException
*/
void write() throws IOException;


/** *//**
* 当channel发生错误时调用.
* @param e
*/
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)
{
// 创建新的Selector

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();
}

// 如果select返回大于0,处理事件

if (n > 0)
{
for (final Iterator<SelectionKey> i = selector.selectedKeys()

.iterator(); i.hasNext();)
{
// 得到下一个Key
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
{
// System.err.println("Ooops");
}

} 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;
}
}
// do select
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的连接
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(InetSocketAddress remoteAddress,
// InetSocketAddress localAddress, String address)
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:
//
218.207.101.236:554/mobile/3/67A451E937422331 RTSP/1.0
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:
//
218.207.101.236:554/mobile/3/67A451E937422331/8jH5QPU5GWS07Ugn.sdp RTSP/1.0
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:
//
218.207.101.236:554/mobile/3/67A451E937422331/8jH5QPU5GWS07Ugn.sdp/
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:
//
218.207.101.236:554/mobile/3/67A451E937422331/8jH5QPU5GWS07Ugn.sdp/trackID=1
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:
//
218.207.101.236:554/mobile/3/67A451E937422331/8jH5QPU5GWS07Ugn.sdp RTSP/1.0
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:
//
218.207.101.236:554/mobile/3/67A451E937422331/8jH5QPU5GWS07Ugn.sdp/trackID=1;seq=0;rtptime=0

PAUSE rtsp:
//
218.207.101.236:554/mobile/3/67A451E937422331/8jH5QPU5GWS07Ugn.sdp/ RTSP/1.0
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:
//
218.207.101.236:554/mobile/3/67A451E937422331/8jH5QPU5GWS07Ugn.sdp/ RTSP/1.0
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的常用命令.