参考:
从零开始写一个RTSP服务器(八)一个多播的RTSP服务器
aac文件:
test.aac文件地址
-
服务端往组播ip+port发送AACRtp数据,循环发送
-
rtsp的响应:DESCRIBE和SETUP修改
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
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 {
int rtcpPort = MulticastServer.start();
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;
try {
System.out.println("TCP已连接===>"+socket.getRemoteSocketAddress());
inputStream = socket.getInputStream();
outputStream = socket.getOutputStream();
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,rtcpPort);
} 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();
}).start();
public static void handlerReceiveData(OutputStream outputStream, byte[] buffer,int rtcpPort){
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;multicast;destination=%s;source=%s;port=%d-%d;ttl=255\r\n"+
"Session: 66334873\r\n"+
"\r\n",
cseq,
MulticastServer.MULTICAST_ADDRESS,
url,
MulticastServer.PORT,
rtcpPort);
}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"+
"a=type:broadcast\r\n"+
"a=rtcp-unicast: reflection\r\n"+
"m=audio %d RTP/AVP 97\r\n"+
"c=IN IP4 %s/255\r\n"+
"a=rtpmap:97 mpeg4-generic/44100/2\r\n"+
"a=fmtp:97 SizeLength=13;\r\n"+
"a=control:track0\r\n",
new Date().getTime(),
localIp,
MulticastServer.PORT,
MulticastServer.MULTICAST_ADDRESS);
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);
}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);
try {
outputStream.write(responseStr.getBytes());
outputStream.flush();
System.out.println("TCP-----------------响应responseStr----------------------");
System.out.println(responseStr);
System.out.println("TCP-----------------响应responseStr----------------------");
} catch (IOException e) {
e.printStackTrace();
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.MulticastSocket;
* IP组播:
* 组播IP地址:用于标识一个IP组播组,范围是从224.0.0.0到239.255.255.255。
* 组播组可以是永久的也可以是临时的:
* - 组播组地址中,有一部分由官方分配的,称为永久组播组。
* - 永久组播组保持不变的是它的ip地址,组中的成员构成可以发生变化。
* - 永久组播组中成员的数量都可以是任意的,甚至可以为零。
* - 那些没有保留下来供永久组播组使用的ip组播地址,可以被临时组播组利用。
* - 224.0.0.0~224.0.0.255为预留的组播地址(永久组地址)。
* - 224.0.1.0~238.255.255.255为用户可用的组播地址(临时组地址),全网范围内有效。
* - 239.0.0.0~239.255.255.255为本地管理组播地址,仅在特定的本地范围内有效。
* 组播源:信息的发送者,组播源不一定属于组播组,它向组播组发送数据,自己不一定是接收者。多个组播源可以同时向一个组播组发送报文。
* 组播组:信息接收者,加入同一组播组的接收者成员可以广泛分布在网络中的任何地方,没有地域限制。
* 组播路由器:支持组播信息传输的所有路由器
* 1.IP协议规定组播地址的范围是224.0.0.0 ~ 239.255.255.255,协议软件底层设定好的
* 2.java提供了
* - socket=new MulticastSocket(组播port);创建组播成员(port端口)
* - socket.joinGroup(InetAddress.getByName(组播ip));加入组播地址
* - 该socket可以接收发往组播地址的包
* 3.往组播地址发送udp包,所有加入该组播ip+port的成员都能收到
* - byte[] data="哈哈哈".getBytes();
* - DatagramPacket packet=new DatagramPacket(data,data.length,InetAddress.getByName(组播ip),组播port);
* - DatagramSocket datagramSocket=new DatagramSocket();//创建DatagramSocket对象
* - datagramSocket.send(packet);//向目标ip+port端发送数据报
* - datagramSocket.close();
public class MulticastServer {
public static String MULTICAST_ADDRESS = "225.0.0.1";
public static int PORT = 5555;
private static MulticastSocket multicastSocket;
public static int start() throws IOException {
multicastSocket = new MulticastSocket(PORT);
multicastSocket.joinGroup(InetAddress.getByName(MULTICAST_ADDRESS));
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
try {
byte[] buf = new byte[1024*1024];
DatagramPacket datagramPacket = new DatagramPacket(buf,buf.length);
multicastSocket.receive(datagramPacket);
handlerReceiveData(0,datagramPacket);
} catch (IOException e) {
e.printStackTrace();
}).start();
RTPAACServer rtph264Server = new RTPAACServer(multicastSocket, InetAddress.getByName(MULTICAST_ADDRESS), PORT);
new Thread(new Runnable() {
@Override
public void run() {
try {
rtph264Server.startSendRtpPackage();
} catch (Exception e) {
e.printStackTrace();
}).start();
DatagramSocket rtcpUdpSocket = new DatagramSocket();
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(1,datagramPacket);
} catch (IOException e) {
e.printStackTrace();
break;
});
return rtcpUdpSocket.getLocalPort();
public static void handlerReceiveData(int type,DatagramPacket datagramPacket){
if(type==0){
}else{
System.out.println("rtcp");
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 RTPAACServer {
private RandomAccessFile in;
private List<ADTSHeader> ADTSIndexs = new ArrayList<>() ;
DatagramSocket rtpUdpSocket;
InetAddress clientAddress;
int clientRtpPort;
public RTPAACServer(DatagramSocket socket, InetAddress address, int port) throws SocketException {
rtpUdpSocket = socket;
clientAddress = address;
clientRtpPort = port;
public void close(){
if(rtpUdpSocket!=null){
rtpUdpSocket.close();
public void startSendRtpPackage() throws Exception {
String fileName = RTPAACServer.class.getResource("test.aac").getPath();
in = new RandomAccessFile(fileName, "r");
parseIndexs();
sendADTSRtpPackage();
in.close();
* AAC的音频文件格式有ADIF & ADTS:
* - ADIF:Audio Data Interchange Format音频数据交换格式,ADIF只有一个统一的头,所以必须得到所有的数据后解码。
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | ADIF header | AAC data |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* - ADTS:Audio Data Transport Stream是AAC音频的传输流格式,ADTS每一帧都有头信息,可以在任意帧解码。
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | ADTS header | AAC data | ADTS header | AAC data |......
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* |fixed28bits|variable28bits|
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
public void parseIndexs() throws Exception {
boolean isEnd=false;
while(true) {
if(in.length()>0) {
Long index = in.getFilePointer();
byte[] header=new byte[7];
in.read(header);
if(((header[0]&0xFF)==255)&&((header[1] & 0xF0)==240)){
ADTSHeader adtsHeader = new ADTSHeader();
adtsHeader.index = index;
adtsHeader.syncword = 0xFFF;
adtsHeader.id = (header[1] & 0x08)>>3;
adtsHeader.layer = (header[1] & 0x06)>>1;
adtsHeader.protectionAbsent = header[1] & 0x01;
adtsHeader.profile = (header[2] & 0xc0)>>6;
adtsHeader.samplingFreqIndex = (header[2] & 0x3c)>>2;
adtsHeader.privateBit = (header[2] & 0x02)>>1;
adtsHeader.channelCfg = (((header[2] & 0x01)<<2) | ((header[3] & 0xc0)>>6));
adtsHeader.originalCopy = (header[3] & 0x20)>>5;
adtsHeader.home = (header[3] & 0x10)>>4;
adtsHeader.copyrightIdentificationBit = (header[3] & 0x08)>>3;
adtsHeader.copyrightIdentificationStart = (header[3] & 0x04)>>2;
adtsHeader.aacFrameLength = (((header[3] & 0x03)<<11) | (header[4]<< 3) | ((header[5]&0xe0)>> 5));
adtsHeader.adtsBufferFullness = (((header[5] & 0x1f)<<6) | ((header[6] & 0xfc)>>2));
adtsHeader.numberOfRawDataBlockInFrame = header[6] & 0x03;
ADTSIndexs.add(adtsHeader);
Long nextStart = in.getFilePointer() + adtsHeader.aacFrameLength-7;
if(in.length()-1>=nextStart){
in.seek(nextStart);
}else{
isEnd=true;
}else{
throw new Exception("failed to parse adts header");
if(isEnd) {
break;
* 获取每一帧ADTS
public void sendADTSRtpPackage() throws IOException, InterruptedException {
* 如果采样频率是44100
* 一般AAC每个1024个采样为一帧
* 所以一秒就有 44100 / 1024 = 43帧
* 时间增量就是 44100 / 43 = 1025
* 一帧的时间为 1 / 43 = 23ms
int timestamp_increse = 44100/(44100/1024);
int PT = 97;
int packageSize = 1400;
int seqNum = 1;
int ts_current = 1;
for(int i=0;i<ADTSIndexs.size();) {
in.seek(ADTSIndexs.get(i).index+7);
int len = ADTSIndexs.get(i).aacFrameLength-7;
byte[] aacADTSDataArr=new byte[len];
in.read(aacADTSDataArr);
byte[] bytes = aacDataToRtp(aacADTSDataArr, PT, seqNum, ts_current, 0x88923423);
DatagramPacket packet2=new DatagramPacket(bytes,bytes.length,clientAddress,clientRtpPort);
try {
rtpUdpSocket.send(packet2);
} catch (IOException e) {
e.printStackTrace();
break;
ts_current+=timestamp_increse;
seqNum ++;
Thread.sleep(22);
if(i==ADTSIndexs.size()-1){
i=0;
}else{
i++;
* 一个ADTS帧数据一个rtp包
* AAC的RTP打包方式:
* Rtp头+4byte(0x00+0x10+aac_data_size13bit)+AAC_Data
public byte[] aacDataToRtp(byte[] aacADTSDataArr,int PT,int seq,int timestamp,int ssrc){
byte[] rtpHeader = initRTPHeader(PT, seq, timestamp, ssrc);
byte[] rtpPackage=new byte[12+4+aacADTSDataArr.length];
System.arraycopy(rtpHeader,0,rtpPackage,0,12);
rtpPackage[12] = 0;
rtpPackage[13] = 0x10;
rtpPackage[14] = (byte) ((aacADTSDataArr.length & 0xFF)>>5);
rtpPackage[15] = (byte) ((aacADTSDataArr.length & 0x1F)<<3);
System.arraycopy(aacADTSDataArr,0,rtpPackage,16,aacADTSDataArr.length);
return rtpPackage;
* 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 & 0xff | 0x80);
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;
* - ADTS:Audio Data Transport Stream是AAC音频的传输流格式,ADTS每一帧都有头信息,可以在任意帧解码。
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | ADTS header | AAC data | ADTS header | AAC data |......
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* |fixed28bits|variable28bits|
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
static class ADTSHeader{
Long index;
int syncword;
int id;
int layer;
int protectionAbsent;
int profile;
int samplingFreqIndex;
int privateBit;
int channelCfg;
int originalCopy;
int home;
int copyrightIdentificationBit;
int copyrightIdentificationStart;
int aacFrameLength;
int adtsBufferFullness;
int numberOfRawDataBlockInFrame;
public int formatSamplingFreqIndex(int samplingFreqIndex){
if((samplingFreqIndex & 0x0f)==0x0){
return 96000;
}else if((samplingFreqIndex & 0x0f)==0x1){
return 88200;
}else if((samplingFreqIndex & 0x0f)==0x2){
return 64000;
}else if((samplingFreqIndex & 0x0f)==0x3){
return 48000;
}else if((samplingFreqIndex & 0x0f)==0x4){
return 44100;
}else if((samplingFreqIndex & 0x0f)==0x5){
return 32000;
}else if((samplingFreqIndex & 0x0f)==0x6){
return 24000;
}else if((samplingFreqIndex & 0x0f)==0x7){
return 22050;
}else if((samplingFreqIndex & 0x0f)==0x8){
return 16000;
}else if((samplingFreqIndex & 0x0f)==0x9){
return 12000;
}else if((samplingFreqIndex & 0x0f)==0xa){
return 11025;
}else if((samplingFreqIndex & 0x0f)==0xb){
return 8000;
}else if((samplingFreqIndex & 0x0f)==0xc){
return 7350;
return 0;
- 运行
RtspTcpServer.java
的mian()
就跑起来了
- 使用vlc播放器播放网络流(文件=>打开网络=>输入URL)
rtsp://127.0.0.1:8888
VLC播放器
打开URl播放
从零开始写一个RTSP服务器(一)不一样的RTSP协议讲解
从零开始写一个RTSP服务器(二)RTSP协议的实现
从零开始写一个RTSP服务器(三)RTP传输H.264
从零开始写一个RTSP服务器(四)一个传输H.264的RTSP服务器
从零开始写一个RTSP服务器(五)RTP传输AAC
从零开始写一个RTSP服务器(六)一...
一、DatagramPacket类:
如果把DatagramSocket比作创建的港口码头,那么DatagramPacket就是发送和接收数据的集装箱。
构造函数:一个用来接收数据,一个用来发送数据
publicDatagra
rtsp组播在一些场景下比单播更合适,比如电子教室等,单播每一路都要占相同带宽,带宽要求比较高,并且路数多了也容易丢包。组播只占一路带宽。不过组播需要路由器,交换机支持(比如 IGMP协议,组播组协议等). 并且有些无线路由器不支持。另外组播只能通过rtp over udp的形式传输。不支持tcp传输.
rtsp组播实现方式,首先sdp和点播不同,sdp需要包含rtp端口...
---------------------------------------------2021/5/30----------------------------------------------
进行客户端和服务器端的rtsp协议通信,客户端使用java代码编写,服务器端使用vlc代替(已成功)。其中要注意的是客户端发送SETUP请求是需要把trackID号带上。
---------------------------------------------2021/6/1----------------
Android局域网广播是一种在同一局域网内,通过广播数据包进行通信的方式。它通过多播地址来发送数据报文,能够快速且有效地将数据传递给其他设备,同时不会占用大量网络资源。
在Android平台上,通过WifiManager类提供了一些API用于实现局域网广播功能。具体而言,可以通过创建MulticastLock实例来申请多播锁,保证网络连接不被挂起。然后,通过创建DatagramPacket实例和DatagramSocket实例,使用send方法进行发送广播数据。最后,在接收端通过创建MulticastSocket和DatagramPacket实例,使用receive方法接收数据包即可。
Android局域网广播可以用于多人游戏、共享文件等场景,提高了设备之间的通信效率和便利程度。需要注意的是,广播发送的数据量要小且频率低,以避免网络拥塞和占用大量资源,同时也要考虑安全问题,避免通过广播漏洞被攻击。