RTSP客户端的java实现

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

    @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抓包的截屏:




  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值