import java.io.*;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Arrays;
import java.util.Date;
public class RtspTcpServer {
public static void main(String[] args) throws IOException, InterruptedException {
ServerSocket serverSocket = new ServerSocket(8888);
System.out.println("TCP服务端端启动===>"+serverSocket.getLocalSocketAddress());
while (true){
Socket socket = serverSocket.accept();
new Thread(new Runnable() {
@Override
public void run() {
InputStream inputStream = null;
OutputStream outputStream = null;
RTPServer rtpServer = null;
try {
System.out.println("TCP已连接===>"+socket.getRemoteSocketAddress());
inputStream = socket.getInputStream();
outputStream = socket.getOutputStream();
rtpServer = new RTPServer();
rtpServer.setClientAddress(InetAddress.getByName(socket.getInetAddress().getHostAddress()));
byte[] buffer = new byte[1024*1024];
int readNum = 0;
while((readNum=inputStream.read(buffer))!=-1){
if(readNum>0){
byte[] receive = Arrays.copyOfRange(buffer,0,readNum);
System.out.println("读取的字节数:"+readNum);
System.out.println("读取的字节数:"+receive.length);
System.out.println("缓冲区大小:"+buffer.length);
handlerReceiveData(outputStream,receive,rtpServer);
} catch (IOException e) {
e.printStackTrace();
}finally {
System.out.println("断开连接");
if(inputStream!=null){
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
if(outputStream!=null){
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
if(socket!=null){
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
rtpServer.close();
}).start();
public static void handlerReceiveData(OutputStream outputStream,byte[] buffer,RTPServer rtpServer){
String receiveStr=new String(buffer);
System.out.println("TCP-----------------接收receiveStr----------------------");
System.out.println(receiveStr);
System.out.println("TCP-----------------接收receiveStr----------------------");
String lines[] = receiveStr.split("\\r?\\n");
int cseq=0;
int clientRtpPort=0;
int clientRtcpPort=0;
String url=null;
String localIp=null;
for(String line:lines){
if(line.indexOf("rtsp://")>-1){
url = line.split("\\s+")[1];
String[] split = line.split(":");
localIp = split[1].substring(2);
if(line.startsWith("CSeq:")){
String[] split = line.split(": ");
cseq = Integer.parseInt(split[1].trim());
if(line.startsWith("Transport:")){
String[] split = line.split(";");
for(String i : split){
if(i.startsWith("client_port=")){
String substring = i.substring(12);
String[] split1 = substring.split("-");
clientRtpPort = Integer.parseInt(split1[0].trim());
clientRtcpPort = Integer.parseInt(split1[1].trim());
}
String responseStr=null;
if (receiveStr.startsWith("OPTIONS")){
responseStr=String.format("RTSP/1.0 200 OK\r\n"+
"CSeq: %d\r\n"+
"Public: OPTIONS, DESCRIBE, SETUP, PLAY\r\n"+
"\r\n",cseq);
}else if(receiveStr.startsWith("SETUP")){
responseStr=String.format("RTSP/1.0 200 OK\r\n"+
"CSeq: %d\r\n"+
"Transport: RTP/AVP;unicast;client_port=%d-%d;server_port=%d-%d\r\n"+
"Session: 66334873\r\n"+
"\r\n",
cseq,
clientRtpPort,
clientRtcpPort,
rtpServer.getRtpPort(),
rtpServer.getRtcpPort());
rtpServer.setClientRtpPort(clientRtpPort);
rtpServer.setClientRtcpPort(clientRtcpPort);
}else if(receiveStr.startsWith("DESCRIBE")){
String sdp=String.format("v=0\r\n"+
"o=- 9%d 1 IN IP4 %s\r\n"+
"t=0 0\r\n"+
"a=control:*\r\n"+
"m=video 0 RTP/AVP 96\r\n"+
"a=rtpmap:96 H264/90000\r\n"+
"a=control:track0\r\n",
new Date().getTime(), localIp);
responseStr=String.format("RTSP/1.0 200 OK\r\nCSeq: %d\r\n"+
"Content-Base: %s\r\n"+
"Content-type: application/sdp\r\n"+
"Content-length: %d\r\n\r\n"+
"%s",
cseq,
url,
sdp.length(),
sdp);
}else if(receiveStr.startsWith("PLAY")){
responseStr=String.format("RTSP/1.0 200 OK\r\n"+
"CSeq: %d\r\n"+
"Range: npt=0.000-\r\n"+
"Session: 66334873; timeout=60\r\n" +
"\r\n",
cseq);
try {
rtpServer.startSendRtpPackage();
} catch (Exception e) {
e.printStackTrace();
}else if(receiveStr.startsWith("PAUSE")){
responseStr=String.format("RTSP/1.0 200 OK\r\n" +
"CSeq: %d\r\n" +
"\r\n",cseq);
}else if(receiveStr.startsWith("TEARDOWN")){
responseStr=String.format("RTSP/1.0 200 OK\r\n" +
"CSeq: %d\r\n" +
"\r\n",cseq);
System.out.println("TCP-----------------响应responseStr----------------------");
System.out.println(responseStr);
System.out.println("TCP-----------------响应responseStr----------------------");
try {
outputStream.write(responseStr.getBytes());
outputStream.flush();
} catch (IOException e) {
e.printStackTrace();
import java.io.IOException;
import java.io.RandomAccessFile;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.List;
public class RTPServer {
private RandomAccessFile in;
private List<Long> NALUIndexs = new ArrayList<>() ;
InetAddress clientAddress;
int clientRtpPort;
int clientRtcpPort;
DatagramSocket rtpUdpSocket;
DatagramSocket rtcpUdpSocket;
public RTPServer() throws SocketException {
rtpUdpSocket = new DatagramSocket();
rtcpUdpSocket = new DatagramSocket();
System.out.println("rtp UDP socket启动===>"+rtpUdpSocket.getLocalSocketAddress());
System.out.println("rtcp UDP socket启动===>"+rtcpUdpSocket.getLocalSocketAddress());
new Thread(new Runnable() {
@Override
public void run() {
byte[] buf = new byte[1024*1024];
DatagramPacket datagramPacket = new DatagramPacket(buf,buf.length);
while (true){
try {
rtpUdpSocket.receive(datagramPacket);
System.out.println("rtp UDP接收包===>"+datagramPacket.getSocketAddress());
handlerReceiveData(datagramPacket);
} catch (IOException e) {
e.printStackTrace();
break;
});
new Thread(new Runnable() {
@Override
public void run() {
byte[] buf = new byte[1024*1024];
DatagramPacket datagramPacket = new DatagramPacket(buf,buf.length);
while (true){
try {
rtcpUdpSocket.receive(datagramPacket);
System.out.println("rtp UDP接收包===>"+datagramPacket.getSocketAddress());
handlerReceiveData(datagramPacket);
} catch (IOException e) {
e.printStackTrace();
break;
});
public void handlerReceiveData(DatagramPacket datagramPacket){
System.out.println("接收到参数:"+datagramPacket.getSocketAddress());
System.out.println(datagramPacket.getData());
System.out.println(datagramPacket.getLength());
public void close(){
if(rtpUdpSocket!=null){
rtpUdpSocket.close();
if(rtcpUdpSocket!=null){
rtcpUdpSocket.close();
public void startSendRtpPackage() throws Exception {
String fileName = RTPServer.class.getResource("test.h264").getPath();
in = new RandomAccessFile(fileName, "r");
parseIndexs();
sendNALURtpPackage();
in.close();
* 获取所有NAUL的起始位置
public void parseIndexs() throws IOException {
while(true) {
if(in.length()>0&&parseNALU()>0) {
NALUIndexs.add(in.getFilePointer());
if(in.length()-in.getFilePointer()<4) {
break;
* H.264原始码流:由多个NALU组成
* 每个NALU之间用起始码(0x000001(3Byte)或0x00000001(4Byte))分割
* H.264编码时,在每个NAL前添加起始码0x000001,解码器在码流中检测到起始码,当前NAL结束;
* 为了防止NAL内部出现0x000001的数据,h.264又提出'防止竞争 emulation prevention"机制,
* 在编码完一个NAL时,如果检测出有连续两个0x00字节,就在后面插入一个0x03;
* 当解码器在NAL内部检测到 0x000003的数据,就把0x03抛弃,恢复原始数据
public int parseNALU() throws IOException {
int head = in.readInt();
if(head==1) {
return 4;
}else if(head>>8 == 1) {
in.seek(in.getFilePointer()-1);
return 3;
return -1;
* 获取每一帧NALU 并存入集合
public void sendNALURtpPackage() throws IOException, InterruptedException {
int framerate = 25;
int timestamp_increse = (int) (90000.0 / framerate);
int PT = 96;
int packageSize = 1400;
int seqNum = 0;
int ts_current = 0;
for(int i=0;i<NALUIndexs.size();i++) {
in.seek(NALUIndexs.get(i));
int len = 0;
if(i!=NALUIndexs.size()-1) {
len = (int) (NALUIndexs.get(i+1)-NALUIndexs.get(i));
}else {
len = (int) (in.length() - NALUIndexs.get(i));
byte[] h264NALUArr=new byte[len];
in.read(h264NALUArr);
List<byte[]> bytes = h264DataToRtp(h264NALUArr, packageSize, PT, seqNum, ts_current, 0x88923423);
for(byte[] arr:bytes){
DatagramPacket packet2=new DatagramPacket(arr, arr.length,clientAddress,clientRtpPort);
try {
rtpUdpSocket.send(packet2);
} catch (IOException e) {
e.printStackTrace();
ts_current+=timestamp_increse;
seqNum += bytes.size();
* H.264的RTP打包方式:
* 1.单NALU打包:一个RTP包中包含一个完整的NALU
* 2.聚合打包:对于较小的NALU,一个RTP包可包含多个完整的NALU
* 3.分片打包:对于较大的NALU,一个NALU可以分为多个RTP包发送
* - 在RTP载荷开始有两个字节的信息,然后再是NALU的内容
* - 第一个字节位(F1|R2|Type5)Type=28
* - 第二个字节位(S1|E1|Type5)S是否第一包;E是否最后一包;
public List<byte[]> h264DataToRtp(byte[] h264NALUArr,int packageSize,int PT,int seq,int timestamp,int ssrc){
List<byte[]> res=new ArrayList();
int seqNum = seq;
if(h264NALUArr.length<=packageSize){
byte[] rtpHeader = initRTPHeader(PT, seqNum, timestamp, ssrc);
byte[] rtpPackage=new byte[12+h264NALUArr.length];
System.arraycopy(rtpHeader,0,rtpPackage,0,12);
System.arraycopy(h264NALUArr,0,rtpPackage,12,h264NALUArr.length);
res.add(rtpPackage);
}else{
* 0 1 2
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | FU indicator | FU header | FU payload ... |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* |F|NRI| Type |S|E|R| Type |
* +---------------+--------------+
byte head=h264NALUArr[0];
int pktNum = (h264NALUArr.length-1)/packageSize;
int endPktSize = (h264NALUArr.length-1)%packageSize;
int currentNum = 0;
while (currentNum <= pktNum){
if(currentNum<pktNum){
byte[] rtpHeader = initRTPHeader(PT, seqNum, timestamp, ssrc);
byte[] rtpPackage=new byte[12+2+packageSize];
System.arraycopy(rtpHeader,0,rtpPackage,0,12);
rtpPackage[12]= (byte) (head & 0x60 |(byte) (28));
if(currentNum==0){
rtpPackage[13]= (byte) (0x80 | ((byte) (head & 0x1f)));
}else if(currentNum==pktNum-1&&endPktSize==0){
rtpPackage[13]= (byte) (0x40 | ((byte) (head & 0x1f)));
}else{
rtpPackage[13]= (byte) (head & 0x1f);
System.arraycopy(h264NALUArr,currentNum*packageSize+1,rtpPackage,14,packageSize);
res.add(rtpPackage);
seqNum+=1;
}else if(currentNum==pktNum&&endPktSize>0){
byte[] rtpHeader = initRTPHeader(PT, seqNum, timestamp, ssrc);
byte[] rtpPackage=new byte[12+2+endPktSize];
System.arraycopy(rtpHeader,0,rtpPackage,0,12);
rtpPackage[12]= (byte) (head & 0x60 |(byte) (28));
rtpPackage[13]= (byte) (0x40 | ((byte) (head & 0x1f)));
System.arraycopy(h264NALUArr,currentNum*packageSize+1,rtpPackage,14,endPktSize);
res.add(rtpPackage);
seqNum+=1;
currentNum++;
return res;
* RTP报文格式:
* |===============================================================|
* | 0 | 1 | 2 | 3 |
* |===============|===============|===============|===============|
* |7 6 5 4 3 2 1 0|7 6 5 4 3 2 1 0|7 6 5 4 3 2 1 0|7 6 5 4 3 2 1 0|
* |===============|===============|===============================|
* |V2|P1|X1|CC4 |M1| PT7 | sequence number16 |
* |===============================================================|
* | timestamp时间戳 |
* |===============================================================|
* |同步信源(SSRC)标识符synchronization source (SSRC) identifier |
* |===============================================================|
* |特约信源(CSRC)标识符contributing source (CSRC) identifiers |
* | .... |
* |===============================================================|
public byte[] initRTPHeader(int PT,int seq,int timestamp,int ssrc){
byte[] headerArr = new byte[12];
for (int i = 0; i < headerArr.length; i++) { headerArr[i] = (byte) 0; }
headerArr[0] = (byte) 0x80;
headerArr[1] = (byte)(PT & 0x7f);
System.arraycopy(intToBytes(seq,2),0,headerArr,2,2);
System.arraycopy(intToBytes(timestamp,4),0,headerArr,4,4);
System.arraycopy(intToBytes(ssrc,4),0,headerArr,8,4);
return headerArr;
* 将32位长度转换为n字节。(大端字节序:高位在前,低位在后)
* @param ldata 将从中构造n字节数组的int。
* @param n 要将长文件转换为的所需字节数。
* @return 用长值填充的所需字节数组。
public byte[] intToBytes(int ldata, int n) {
byte[] buff = new byte[n];
for (int i=n-1;i>=0;i--) {
buff[i] = (byte)ldata;
ldata = ldata>>8;
return buff;
public int getRtpPort(){
return rtpUdpSocket.getLocalPort();
public int getRtcpPort(){
return rtcpUdpSocket.getLocalPort();
public InetAddress getClientAddress() {
return clientAddress;
public void setClientAddress(InetAddress clientAddress) {
this.clientAddress = clientAddress;
public int getClientRtpPort() {
return clientRtpPort;
public void setClientRtpPort(int clientRtpPort) {
this.clientRtpPort = clientRtpPort;
public int getClientRtcpPort() {
return clientRtcpPort;
public void setClientRtcpPort(int clientRtcpPort) {
this.clientRtcpPort = clientRtcpPort;
- 运行
RtspTcpServer.java
的mian()
就跑起来了

- 使用vlc播放器播放网络流(文件=>打开网络=>输入URL)
rtsp://127.0.0.1:8888
VLC播放器
打开URl播放
那么rtsp我们怎么处理呢?我们使用java将rtsp流拿到之后,进行rtsp解码,因为要考虑到延时性,所以我们要尽量拿到一秒的数据之后再进行推流处理。代码如下:
public void decodeWave(String token, String url) throws Ex..
---------------------------------------------2021/5/30----------------------------------------------
进行客户端和服务器端的rtsp协议通信,客户端使用java代码编写,服务器端使用vlc代替(已成功)。其中要注意的是客户端发送SETUP请求是需要把trackID号带上。
---------------------------------------------2021/6/1----------------
UDP通信是一种无连接的通信协议,它提供了一种快速、简单和不可靠的数据传输方式。同时,H.264/H.265是一种高效的视频编码标准,可以将视频压缩至较小的码流,从而实现更高效的网络视频传输。
对于输出H.264/H.265的码流,可以使用一些常见的网络视频传输协议,如RTP、RTSP和ONVIF。这些协议都支持H.264/H.265视频流的传输,并提供了一些常见的功能,如实时视频播放、回放和录制等。
具体实现上,可以使用一些开源的视频编码和传输库,如FFmpeg和Live555等。这些库提供了丰富的编码和传输功能,并且可以与各种主流的操作系统和开发平台集成。
总之,输出H.264/H.265的码流需要选择合适的通信协议和编码库,并进行相应的配置和调试。对于不同的应用场景,还需要考虑一些额外的因素,如网络带宽、延迟和稳定性等。