RTSP,Java实现简单的RTSP报文交换
Posted 梦鸢MoYuan
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了RTSP,Java实现简单的RTSP报文交换相关的知识,希望对你有一定的参考价值。
这里写目录标题
- 了解RTSP协议
- 使用Java程序编写RTSP客户端 访问 RTSP服务端,实现拉流
RTSP协议是什么
RTSP是一种基于文本的协议,用CRLF(回车换行)作为每一行的结束符,其好处是,在使用过程中可以方便地增加自定义参数,也方便抓包分析。从消息传送方向上来分,RTSP的报文有两类:请求报文和响应报文。请求报文是指从客户端向服务器发送的请求(也有少量从服务器向客户端发送的请求),响应报文是指从服务器到客户端的回应。
RTSP请求报文的常用方法与作用
一次基本的RTSP交互过程如下,C表示客户端,S表示服务端。
- OPTION请求
-> 响应 - DESCRIBE请求
-> 响应
如果响应无权限, 那么需要带上用户名密码 - SETUP请求
-> 响应 - PLAY请求
-> 响应
-> 流数据
-> 流数据
-> 流数据
报文实例:
1. OPTIONS
OPTIONS rtsp://39.170.35.150:1554/h264/ch0/1 RTSP/1.0
CSeq: 2
User-Agent: LibVLC/3.0.16 (LIVE555 Streaming Media v2016.11.28)
RTSP/1.0 200 OK
CSeq: 2
Public: OPTIONS, DESCRIBE, PLAY, PAUSE, SETUP, TEARDOWN, SET_PARAMETER, GET_PARAMETER
Date: Sat, Mar 05 2022 15:39:55 GMT
2. DESCRIBE
DESCRIBE rtsp://39.170.35.150:1554/h264/ch0/1 RTSP/1.0
CSeq: 3
User-Agent: LibVLC/3.0.16 (LIVE555 Streaming Media v2016.11.28)
Accept: application/sdp
RTSP/1.0 401 Unauthorized
CSeq: 3
WWW-Authenticate: Digest realm="2857be191e08", nonce="5b960b3d4673be2908666321f64d2bff", stale="FALSE"
WWW-Authenticate: Basic realm="2857be191e08"
Date: Sat, Mar 05 2022 15:39:55 GMT
发现无权, 使用用户名密码进行授权
DESCRIBE rtsp://39.170.35.150:1554/h264/ch0/1 RTSP/1.0
CSeq: 4
Authorization: Digest username="admin", realm="2857be191e08", nonce="5b960b3d4673be2908666321f64d2bff", uri="rtsp://39.170.35.150:1554/h264/ch0/1", response="3cfc28bcf70670c2120acc3b5d1357d3"
User-Agent: LibVLC/3.0.16 (LIVE555 Streaming Media v2016.11.28)
Accept: application/sdp
RTSP/1.0 200 OK
CSeq: 4
Content-Type: application/sdp
Content-Base: rtsp://39.170.35.150:1554/h264/ch0/1/
Content-Length: 569
v=0
o=- 1646494795935543 1646494795935543 IN IP4 10.2.144.4
s=Media Presentation
e=NONE
b=AS:5050
t=0 0
a=control:rtsp://39.170.35.150:1554/h264/ch0/1/
m=video 0 RTP/AVP 96
c=IN IP4 0.0.0.0
b=AS:5000
a=recvonly
a=x-dimensions:1920,1080
a=control:rtsp://39.170.35.150:1554/h264/ch0/1/trackID=1
a=rtpmap:96 H264/90000
a=fmtp:96 profile-level-id=420029; packetization-mode=1; sprop-parameter-sets=Z00AKpWoHgCJ+WbgICAgQA==,aO48gA==
a=Media_header:MEDIAINFO=494D4B48010100000400000100000000000000000000000000000000000000000000000000000000;
a=appversion:1.0
4. SETUP
这里得URL后会带上control后的trackID
这里的Transport表示要使用的传输方式, TCP表示使用tcp传输, 也可以使用UDP
SETUP rtsp://39.170.35.150:1554/h264/ch0/1/trackID=1 RTSP/1.0
CSeq: 5
Authorization: Digest username="admin", realm="2857be191e08", nonce="5b960b3d4673be2908666321f64d2bff", uri="rtsp://39.170.35.150:1554/h264/ch0/1/", response="80324f6c8f797633475816843f329b61"
User-Agent: LibVLC/3.0.16 (LIVE555 Streaming Media v2016.11.28)
Transport: RTP/AVP/TCP;unicast;interleaved=0-1
RTSP/1.0 200 OK
CSeq: 5
Session: 2007782907;timeout=60
Transport: RTP/AVP/TCP;unicast;interleaved=0-1;ssrc=762c0e39;mode="play"
Date: Sat, Mar 05 2022 15:39:55 GMT
5. PLAY
PLAY rtsp://39.170.35.150:1554/h264/ch0/1/ RTSP/1.0
CSeq: 6
Authorization: Digest username="admin", realm="2857be191e08", nonce="5b960b3d4673be2908666321f64d2bff", uri="rtsp://39.170.35.150:1554/h264/ch0/1/", response="f88f707756441a9437f162d68ec5adbb"
User-Agent: LibVLC/3.0.16 (LIVE555 Streaming Media v2016.11.28)
Session: 2007782907
Range: npt=0.000-
RTSP/1.0 200 OK
CSeq: 6
Session: 2007782907
RTP-Info: url=rtsp://39.170.35.150:1554/h264/ch0/1/trackID=1;seq=40895;rtptime=1378373026
Date: Sat, Mar 05 2022 15:39:56 GMT
后续就会有二进制流进来.
Java实现简单的RTSP报文交换
RTSP端口默认为:1554
通过TCP对目标主机端口发起连接,然后使用RTSP格式的报文进行交换信息即可。
下面是报文拼凑的代码,使用建立TCP连接后安装RTSP规定的通讯顺序发送即可。
// 定义协议头和通用信息
private String transport = "RTP/AVP/TCP;unicast;interleaved=0-1";
private static final String VERSION = " RTSP/1.0";
private static final String RTSP_OK = "RTSP/1.0 200 OK";
private String address = "rtsp://admin:shinemo123@39.170.35.150:1554/h264/ch0/1"; // RTSP URI 包含目标ip和账号密码
private String sessionid; // RTSP 是有状态的,通讯成功后需要记录 sessionId
private void doTeardown()
StringBuilder sb = new StringBuilder();
sb.append("TEARDOWN ");
sb.append(this.address);
sb.append("/");
sb.append(VERSION);
sb.append(System.lineSeparator());
sb.append("Cseq: ");
sb.append(seq++);
sb.append(System.lineSeparator());
sb.append("User-Agent: RealMedia Player HelixDNAClient/10.0.0.11279 (win32)");
sb.append(System.lineSeparator());
sb.append("Session: ");
sb.append(sessionid);
sb.append(System.lineSeparator());
sb.append(System.lineSeparator());
send(sb.toString().getBytes());
private void doPlay()
StringBuilder sb = new StringBuilder();
sb.append("PLAY ");
sb.append(this.address);
sb.append(VERSION);
sb.append(System.lineSeparator());
sb.append("Session: ");
sb.append(sessionid);
sb.append(System.lineSeparator());
sb.append("Cseq: ");
sb.append(seq++);
sb.append(System.lineSeparator());
sb.append("Range: npt=0.000-");
sb.append(System.lineSeparator());
sb.append(System.lineSeparator());
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(System.lineSeparator());
sb.append("Cseq: ");
sb.append(seq++);
sb.append(System.lineSeparator());
sb.append("Transport: ");
sb.append(transport);
sb.append(System.lineSeparator());
sb.append(System.lineSeparator());
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(System.lineSeparator());
sb.append("Cseq: ");
sb.append(seq++);
sb.append(System.lineSeparator());
sb.append(System.lineSeparator());
send(sb.toString().getBytes());
private void doDescribe()
StringBuilder sb = new StringBuilder();
sb.append("DESCRIBE ").append(this.address).append(VERSION).append(System.lineSeparator());
sb.append("Cseq: ").append(seq++).append(System.lineSeparator());
sb.append("Accept: application/sdp").append(System.lineSeparator());
sb.append(System.lineSeparator());
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(System.lineSeparator());
sb.append("Cseq: ");
sb.append(seq++);
sb.append(System.lineSeparator());
sb.append("Session: ");
sb.append(sessionid);
sb.append(System.lineSeparator());
sb.append(System.lineSeparator());
send(sb.toString().getBytes());
完整的垃圾代码:
这里使用的是netty。
package loki.rtsp;
import javafx.scene.media.Media;
import javafx.scene.media.MediaPlayer;
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";
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 String transport = "RTP/AVP/TCP;unicast;interleaved=0-1";
/** *//** 线程是否结束的标志 */
private AtomicBoolean shutdown;
private int seq=2;
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("端口打开成功" + socketChannel.getLocalAddress() + " -> " + socketChannel.getRemoteAddress());
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();
// doPlay();
System.out.println("PLAY start");
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;
String temp =tmp.substring(tmp.indexOf("trackID"));
trackInfo = temp.split("\\r\\n")[0];
break;
case describe:
String tempSessionId = tmp.substring(tmp.indexOf("Session: ") + 9);
sessionid = tempSessionId.split("\\r\\n")[0];
sessionid = tempSessionId.split(";")[0];
sessionid = sessionid.trim();
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(System.lineSeparator());
sb.append("Cseq: ");
sb.append(seq++);
sb.append(System.lineSeparator());
sb.append("User-Agent: RealMedia Player HelixDNAClient/10.0.0.11279 (win32)");
sb.append(System.lineSeparator());
sb.append("Session: ");
sb.append(sessionid);
sb.append(System.lineSeparator());
sb.append(System.lineSeparator());
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(System.lineSeparator());
sb.append("Session: ");
sb.append(sessionid);
sb.append(System.lineSeparator());
sb.append("Cseq: ");
sb.append(seq++);
sb.append(System.lineSeparator());
sb.append("Range: npt=0.000-");
sb.append(System.lineSeparator());
sb.append(System.lineSeparator());
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(System.lineSeparator());
sb.append("Cseq: ");
sb.append(seq++);
sb.append(System.lineSeparator());
sb.append("Transport: ");
sb.append(transport);
sb.append(System.lineSeparator());
sb.append(System.lineSeparator());
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(System.lineSeparator());
sb.append("Cseq: ");
sb.append(seq++);
sb.append(System.lineSeparator());
sb.append(System.lineSeparator());
System.out.println(sb.toString());
send(sb.toString().getBytes());
private void doDescribe()
StringBuilder sb = new StringBuilder();
sb.append("DESCRIBE ").append(this.address).append(VERSION).append(System.lineSeparator());
sb.append("Cseq: ").append(seq++).append(System.lineSeparator());
sb.append("Accept: application/sdp").append(System.lineSeparator());
sb.append(System.lineSeparator());
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(System.lineSeparator());
sb.append("Cseq: ");
sb.append(seq++);
sb.append(System.lineSeparator());
sb.append("Session: ");
sb.append(sessionid);
sb.append(System.lineSeparator());
sb.append(System.lineSeparator());
send(sb.toString().getBytes());
System.out.println(sb.toString());
public static void main(String[] args)
try
// RTSPClient(InetSocketAddress remoteAddress,
// InetSocketAddress localAddress, String address)
// rtsp://admin:shinemo123@39.170.35.150:1554/h264/ch0/1
RTSPClient client = new RTSPClient(
new InetSocketAddress("39.170.35.150", 1554),
new InetSocketAddress("10.1.65.48", 0),
"rtsp://admin:shinemo123@39.170.35.150:1554/h264/ch0/1");
client.start();
catch (Exception e)
e.printStackTrace();
流媒体专家RTSP协议详解II
以上是关于RTSP,Java实现简单的RTSP报文交换的主要内容,如果未能解决你的问题,请参考以下文章