RTSP协议详解
rtsp和http的区别和联系
。
是有状态的,不同的是RTSP的命令需要知道现在正处于一个什么状态,也就是说rtsp的命令总是按照顺序来发送,某个命令总在另外一个命令之前要发送。
Rtsp不管处于什么状态都不会去断掉连接。,而http则不保存状态,协议在发送一个命令以后,连接就会断开,且命令之间是没有依赖性的。rtsp协议使用554端口,http使用80端口。
Initiation
Protocol),是基于IP的一个应用层控制协议。由于SIP是基于纯文本的信令协议,可以管理不同接入网络上的会话等。会话可以是终端设备之间任何
类型的通信,如视频会话、既时信息处理或协作会话。该协议不会定义或限制可使用的业务,传输、服务质量、计费、安全性等问题都由基本核心网络和其它协议处
理。
(1)联系:sip和rtsp都是应用层的控制协议,负责一次通信过程的建立和控制和结束,不负责中间的传输部分。他们都是基于纯文本的信令协议,穿墙性
能良好。支持tcp、udp,支持多方通信。他们都需要服务器支持,都支持会话中重定向。sip和rtsp
都使用sdp协议来传送媒体参数,使用rtp(rtcp)协议来传输媒体流。
(2)区别:rtsp是专门为流媒体制定的协议,在多个媒体流的时间同步方面比sip强大。rtsp还提供网络负载均衡的功能,减轻服务器压力和网络带宽
要求。sip一般用来创建一次音频、视频通话(双向),而rtsp一般用来做视频点播、视频监控等(单向)。当然,从原理上讲,rtsp也可以做双向的视
频通话。
方法 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。
回应消息格式:
RTSP版本 状态码 解释 CR LF
消息头 CR LF CR LF
消息体 CR LF
其中RTSP版本一般都是RTSP/1.0,状态码是一个数值,200表示成功,解释是与状态码对应的文本解释。
状态码由三位数组成,表示方法执行的结果,定义如下:
1XX:保留,将来使用;
2XX:成功,操作被接收、理解、接受(received,understand,accepted);
3XX:重定向,要完成操作必须进行进一步操作;
4XX:客户端出错,请求有语法错误或无法实现;
5XX:服务器出错,服务器无法实现合法的请求。
中带有trackID=0,表示对该通道进行设置。Transport参数设置了传输模式,包的结构。接下来的数据包头部第二个字节位置就是
interleaved,它的值是每个通道都不同的,trackID=0的interleaved值有两个0或1,0表示rtp包,1表示rtcp包,接
受端根据interleaved的值来区别是哪种数据包。
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的常用命令.
RTSP协议详解的更多相关文章
- (转)RTSP协议详解
转自:https://www.cnblogs.com/lidabo/p/6553212.html RTSP简介 RTSP(Real Time Streaming Protocol)是由Real ...
- Httpd服务进阶知识-HTTP协议详解
Httpd服务进阶知识-HTTP协议详解 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.WEB开发概述 1>.C/S编程 CS即客户端.服务器编程. 客户端.服务端之间需 ...
- HTTP协议详解(转)
转自:http://blog.csdn.net/gueter/archive/2007/03/08/1524447.aspx Author :Jeffrey 引言 HTTP是一个属于应用层的面向对象的 ...
- HTTP协议详解
Author :Jeffrey 引言 HTTP 是一个属于应用层的面向对象的协议,由于其简捷.快速的方式,适用于分布式超媒体信息系统.它于1990年提出,经过几年的使用与发展,得到不断地完善和 扩展. ...
- 动态选路、RIP协议&&OSPF协议详解
动态选路.RIP协议&&OSPF协议详解 概念 当相邻路由器之间进行通信,以告知对方每个路由器当前所连接的网络,这时就出现了动态选路.路由器之间必须采用选路协议进行通信,这样的选路协议 ...
- ASP.NET知识总结(3.HTTP协议详解)
引言 HTTP是一个属于应用层的面向对象的协议,由于其简捷.快速的方式,适用于分布式超媒体信息系统.它于1990年提出,经过几年的使用与发展,得到不断地完善和扩展.目前在WWW中使用的是HTTP/1. ...
- 接口测试之HTTP协议详解
引言 HTTP是一个属于应用层的面向对象的协议,由于其简捷.快速的方式,适用于分布式超媒体信息系统.它于1990年提出,经过几年的使用与发展,得到不断地完善和扩展.目前在WWW中使用的是HTTP/1. ...
- 计算机网络(12)-----HTTP协议详解
HTTP协议详解 http请求 http请求由三部分组成,分别是:请求行.消息报头.请求正文 (1)请求行 请求行以一个方法符号开头,以空格分开,后面跟着请求的URI和协议的版本,格式如下:Metho ...
- OSPF协议详解
CCNP OSPF协议详解 2010-02-24 20:30:22 标签:CCNP 职场 OSPF 休闲 OSPF(Open Shortest Path Fitst,ospf)开放最短路径优先协议,是 ...
随机推荐
- BZOJ3560 : DZY Loves Math V
因为欧拉函数是非完全积性函数,所以可以考虑对每个数进行分解质因数,将每个质数的解乘起来即可. 对于一个质数$p$,设它在各个数中分别出现了$b_1,b_2,...b_n$次,那么由生成函数和欧拉函数的 ...
- 基于SpringMVC框架项目Demo
Git地址:https://github.com/JavaWeb1024/SpringMVC 1. 框架简介: 为打造一套集群高可用的框架,集成的技术目前比较成熟,稳定.相关的知识点在网络上也 ...
- 分享到QQ空间代码(一)
如何给自己的网站添上"分享到QQ空间"的功能? 只要选择以下代码嵌入自己的网页,即可将网站的信息分享到QQ空间
- 针对特定浏览器起作用的CSS: IE Chrome Firefox CSS Hack
Firefox的CSSHack 只在Firefox上应用的CSS Hack,虽然这种情况非常少,但有时也会碰到: @-moz-document url-prefix() { .cssSelector ...
- Swift Internal Parameter and External Parameter 外部参数和内部参数
今天跟大神又学习了些关于IOS开发Swift语言的外部参数和内部参数 func doSomething(num1: Int, num2: Int) -> Int { return num1 + ...
- nginx源码编译安装
安装编译所需的包: [root@xaiofan ~]# yum install -y gcc gcc-c++ autoconf automake 安装nginx使用某些功能需要的包: [root@xa ...
- JavaWEB前端向服务器端发送对象
最近项目中需要做一个关于批量删除的功能,删除条件有多个,需要从页面全部传给后台服务器程序,单个的删除,可以拼接参数给url,服务器端获取参数后执行删除操作即可.但是批量删除多个,参数会很多,传递就有些 ...
- HDU 1698 Just a Hook(线段树区间替换)
Just a Hook Time Limit: 4000/2000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others) Tota ...
- panel,dialog,window组件越界问题汇总
之前分别写过panel,dialog,window三个组件因为拖曳或者reSize造成组件越界而无法还原的问题,两篇文章分别针对拖曳和reSize给出了解决方案.不过根据朋友的反馈,reSize的解决 ...
- java.sql.SQLException: 关闭的连接 解决办法
程序如果长时间不进行数据库操作,那么数据源中的 Connection 很可能已经断开.其原因有可能是防火墙,或者连接的数据库设置的超时时间.这里使用的是 C3P0 连接 oracle 数据库,引起的异 ...