http://hi.baidu.com/ssyuan/item/128bc7d624a77a876dce3fc5
最近弄rtsp方面的东西,想找一个java实现的现成例子,翻遍网络,没有一个好用的,最后只好自己写一个。简单实现的思路是:首先建立一个tcp的连接,来建立rtsp的通道,然后依次按照rtsp协议发送options, describe, setup, play, pause, teardown命令,当然没有处理udp的数据部分,可以用darwin作代理来处理。代码如下:
NIO的socket事件接口:
package com.fountain.test;
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);
}
具体的rtsp的客户端代码:
package com.fountain.test;
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
.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();
}
@Override
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();
}
}
@Override
public void error(Exception e) {
e.printStackTrace();
}
@Override
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("返回内容:"+tmp);
if (tmp.startsWith(RTSP_OK)) {
switch (sysStatus) {
case init:
sysStatus = Status.options;
System.out.println("option ok");
break;
case options:
sysStatus = Status.describe;
trackInfo=tmp.substring(tmp.indexOf("trackID"));
System.out.println("describe ok");
break;
case describe:
sessionid = tmp.substring(tmp.indexOf("Session: ") + 9, tmp
.indexOf("Date:"));
if(sessionid!=null&&sessionid.length()>0){
sysStatus = Status.setup;
System.out.println("setup ok");
}
break;
case setup:
sysStatus = Status.play;
System.out.println("play ok");
break;
case play:
sysStatus = Status.pause;
System.out.println("pause ok");
break;
case pause:
sysStatus = Status.teardown;
System.out.println("teardown ok");
shutdown.set(true);
break;
case teardown:
sysStatus = Status.init;
System.out.println("exit start");
break;
default:
break;
}
isSended=false;
} else {
System.out.println("返回错误:" + tmp);
}
}
public static void main(String[] args) {
try {
RTSPClient client = new RTSPClient(new InetSocketAddress(
"192.168.10.33", 554),
new InetSocketAddress("192.168.10.70", 0),
"rtsp://192.168.10.33:554/live.sdp");
client.start();
} catch (Exception e) {
e.printStackTrace();
}
}
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());
}
}
比较简单的实现,最后程序的日志:
端口打开成功
OPTIONS rtsp://192.168.10.33:554 RTSP/1.0
Cseq: 1
返回内容:RTSP/1.0 200 OK
Server: DSS/5.5.4 (Build/489.13; Platform/Win32; Release/Darwin; )
Cseq: 1
Public: DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE, OPTIONS, ANNOUNCE, RECORD
option ok
DESCRIBE rtsp://192.168.10.33:554/live.sdp RTSP/1.0
Cseq: 2
返回内容:RTSP/1.0 200 OK
Server: DSS/5.5.4 (Build/489.13; Platform/Win32; Release/Darwin; )
Cseq: 2
Cache-Control: no-cache
Content-length: 376
Date: Tue, 22 Jan 2008 06:54:07 GMT
Expires: Tue, 22 Jan 2008 06:54:07 GMT
Content-Type: application/sdp
x-Accept-Retransmit: our-retransmit
x-Accept-Dynamic-Rate: 1
Content-Base: rtsp://192.168.10.33:554/live.sdp/
v=0
o=- 457979544 27 IN IP4 192.168.10.33
s=HelixSession
c=IN IP4 0.0.0.0
t=0 0
a=control:*
m=video 0 RTP/AVP 96
b=AS:20
a=rtpmap:96 MP4V-ES/90000
a=fmtp:96 profile-level-id=8; config=000001B008000001B50EA020202F000001000000012000C788BA9850584121463F
a=framesize:96 176-144
a=cliprect:0,0,144,176
a=mpeg4-esid:201
a=x-envivio-verid:00035A34
a=control:trackID=1
describe ok
SETUP rtsp://192.168.10.33:554/live.sdp/trackID=1
RTSP/1.0
Cseq: 3
Transport: RTP/AVP;UNICAST;client_port=16264-16265;mode=play
返回内容:RTSP/1.0 200 OK
Server: DSS/5.5.4 (Build/489.13; Platform/Win32; Release/Darwin; )
Cseq: 3
Cache-Control: no-cache
Session: 59725815223204
Date: Tue, 22 Jan 2008 06:54:09 GMT
Expires: Tue, 22 Jan 2008 06:54:09 GMT
Transport: RTP/AVP;UNICAST;mode=play;source=192.168.10.33;client_port=16264-16265;server_port=6970-6971
setup ok
PLAY rtsp://192.168.10.33:554/live.sdp RTSP/1.0
Session: 59725815223204
Cseq: 4
返回内容:RTSP/1.0 200 OK
Server: DSS/5.5.4 (Build/489.13; Platform/Win32; Release/Darwin; )
Cseq: 4
Session: 59725815223204
Range: npt=now-
RTP-Info: url=rtsp://192.168.10.33:554/live.sdp/trackID=1
play ok
PAUSE rtsp://192.168.10.33:554/live.sdp/ RTSP/1.0
Cseq: 5
Session: 59725815223204
返回内容:RTSP/1.0 200 OK
Server: DSS/5.5.4 (Build/489.13; Platform/Win32; Release/Darwin; )
Cseq: 5
Session: 59725815223204
pause ok
TEARDOWN rtsp://192.168.10.33:554/live.sdp/ RTSP/1.0
Cseq: 6
User-Agent: RealMedia Player HelixDNAClient/10.0.0.11279 (win32)
Session: 59725815223204
返回内容:RTSP/1.0 200 OK
Server: DSS/5.5.4 (Build/489.13; Platform/Win32; Release/Darwin; )
Cseq: 6
Session: 59725815223204
Connection: Close
teardown ok
端口关闭成功
用wireshark抓包的截屏: