Browse Source

/*SIP协议对接音频协议推流相关的尝试和修改*/

master
htjcAdmin 3 weeks ago
parent
commit
640c732b2b
42 changed files with 1590 additions and 24 deletions
  1. +5
    -0
      pom.xml
  2. +45
    -0
      src/main/java/com/inspect/tcpserver/sip/gb28181/Gb28181PsMuxer.java
  3. +71
    -0
      src/main/java/com/inspect/tcpserver/sip/gb28181/H264AnnexBExtractor.java
  4. +46
    -0
      src/main/java/com/inspect/tcpserver/sip/gb28181/MediaSession.java
  5. +28
    -0
      src/main/java/com/inspect/tcpserver/sip/gb28181/MediaSessionEx.java
  6. +30
    -0
      src/main/java/com/inspect/tcpserver/sip/gb28181/PesPacket.java
  7. +35
    -0
      src/main/java/com/inspect/tcpserver/sip/gb28181/PsHeader.java
  8. +21
    -0
      src/main/java/com/inspect/tcpserver/sip/gb28181/PsMuxer.java
  9. +10
    -0
      src/main/java/com/inspect/tcpserver/sip/gb28181/PsPackHeader.java
  10. +28
    -0
      src/main/java/com/inspect/tcpserver/sip/gb28181/PsPesPacket.java
  11. +12
    -0
      src/main/java/com/inspect/tcpserver/sip/gb28181/PsProgramStreamMap.java
  12. +12
    -0
      src/main/java/com/inspect/tcpserver/sip/gb28181/PsSystemHeader.java
  13. +60
    -0
      src/main/java/com/inspect/tcpserver/sip/gb28181/RtpSender.java
  14. +84
    -0
      src/main/java/com/inspect/tcpserver/sip/gb28181/RtpSenderEx.java
  15. +74
    -0
      src/main/java/com/inspect/tcpserver/sip/gb28181/RtspClient.java
  16. +45
    -0
      src/main/java/com/inspect/tcpserver/sip/media/MediaSession.java
  17. +138
    -0
      src/main/java/com/inspect/tcpserver/sip/media/SdpInfo.java
  18. +92
    -0
      src/main/java/com/inspect/tcpserver/sip/media/SdpParser.java
  19. +24
    -0
      src/main/java/com/inspect/tcpserver/sip/media/rtcp/RtcpPacket.java
  20. +13
    -0
      src/main/java/com/inspect/tcpserver/sip/media/rtcp/RtcpSrSender.java
  21. +37
    -0
      src/main/java/com/inspect/tcpserver/sip/media/rtp/RtpPacket.java
  22. +48
    -0
      src/main/java/com/inspect/tcpserver/sip/media/rtp/RtpPacketizer.java
  23. +11
    -0
      src/main/java/com/inspect/tcpserver/sip/media/rtp/RtpSender.java
  24. +31
    -0
      src/main/java/com/inspect/tcpserver/sip/media/rtp/RtpTcpSender.java
  25. +32
    -0
      src/main/java/com/inspect/tcpserver/sip/media/rtp/RtpUdpSender.java
  26. +21
    -0
      src/main/java/com/inspect/tcpserver/sip/media/rtsp/H264AnnexBExtractor.java
  27. +33
    -0
      src/main/java/com/inspect/tcpserver/sip/media/rtsp/RtspClient.java
  28. +96
    -23
      src/main/java/com/inspect/tcpserver/sip/service/SipClientService.java
  29. +35
    -0
      src/main/java/com/inspect/tcpserver/sip/stream/H264AnnexBExtractor.java
  30. +35
    -0
      src/main/java/com/inspect/tcpserver/sip/stream/MediaSession.java
  31. +15
    -0
      src/main/java/com/inspect/tcpserver/sip/stream/RtpClock.java
  32. +39
    -0
      src/main/java/com/inspect/tcpserver/sip/stream/RtpPacket.java
  33. +46
    -0
      src/main/java/com/inspect/tcpserver/sip/stream/RtpSender.java
  34. +26
    -0
      src/main/java/com/inspect/tcpserver/sip/stream/RtpSocket.java
  35. +59
    -0
      src/main/java/com/inspect/tcpserver/sip/stream/RtspClient.java
  36. +15
    -0
      src/main/java/com/inspect/tcpserver/sip/stream/ps/PsConstants.java
  37. +28
    -0
      src/main/java/com/inspect/tcpserver/sip/stream/ps/PsMuxer.java
  38. +27
    -0
      src/main/java/com/inspect/tcpserver/sip/stream/ps/PsPackHeader.java
  39. +35
    -0
      src/main/java/com/inspect/tcpserver/sip/stream/ps/PsPesPacket.java
  40. +26
    -0
      src/main/java/com/inspect/tcpserver/sip/stream/ps/PsProgramStreamMap.java
  41. +21
    -0
      src/main/java/com/inspect/tcpserver/sip/stream/ps/PsSystemHeader.java
  42. +1
    -1
      src/main/resources/application-dev.yml

+ 5
- 0
pom.xml View File

@ -136,6 +136,11 @@
<groupId>com.fasterxml.jackson.dataformat</groupId> <groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId> <artifactId>jackson-dataformat-xml</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>javacv-platform</artifactId>
<version>1.5.9</version>
</dependency>
</dependencies> </dependencies>


+ 45
- 0
src/main/java/com/inspect/tcpserver/sip/gb28181/Gb28181PsMuxer.java View File

@ -0,0 +1,45 @@
package com.inspect.tcpserver.sip.gb28181;
import java.io.ByteArrayOutputStream;
import java.nio.ByteBuffer;
public class Gb28181PsMuxer {
private boolean headerSent = false;
public byte[] muxH264(byte[] annexB, long pts90k) {
try {
ByteArrayOutputStream out = new ByteArrayOutputStream();
if (!headerSent) {
out.write(PsHeader.packHeader());
out.write(PsHeader.systemHeader());
out.write(PsHeader.psmHeader());
headerSent = true;
}
out.write(PesPacket.buildH264Pes(annexB, pts90k));
return out.toByteArray();
} catch (Exception e) {
return null;
}
}
public byte[] muxOneFrame(byte[] h264, long pts90k) {
try {
ByteArrayOutputStream out = new ByteArrayOutputStream();
// 每一帧都必须有
out.write(PsHeader.packHeader());
out.write(PsHeader.systemHeader());
out.write(PsHeader.psmHeader());
out.write(PesPacket.buildH264Pes(h264, pts90k));
return out.toByteArray();
} catch (Exception e) {
return null;
}
}
}

+ 71
- 0
src/main/java/com/inspect/tcpserver/sip/gb28181/H264AnnexBExtractor.java View File

@ -0,0 +1,71 @@
package com.inspect.tcpserver.sip.gb28181;
import org.bytedeco.ffmpeg.avcodec.AVPacket;
import org.bytedeco.javacpp.BytePointer;
import java.nio.ByteBuffer;
public class H264AnnexBExtractor {
private static final byte[] START_CODE = {0, 0, 0, 1};
/**
* AVPacket 中提取 Annex-B H264
*/
public static byte[] extractFromAvPacket(AVPacket pkt) {
if (pkt == null || pkt.size() <= 0 || pkt.data() == null) {
return null;
}
BytePointer ptr = pkt.data();
int size = pkt.size();
byte[] raw = new byte[size];
ptr.get(raw);
// 已经是 Annex-B
if (isAnnexB(raw)) {
return raw;
}
// AVCC Annex-B
return avccToAnnexB(raw);
}
/**
* 判断是否 Annex-B
*/
private static boolean isAnnexB(byte[] data) {
if (data.length < 4) return false;
return data[0] == 0 && data[1] == 0 && data[2] == 0 && data[3] == 1;
}
/**
* AVCClength-prefixed Annex-Bstart code
*/
private static byte[] avccToAnnexB(byte[] avcc) {
ByteBuffer in = ByteBuffer.wrap(avcc);
ByteBuffer out = ByteBuffer.allocate(avcc.length + 64);
while (in.remaining() >= 4) {
int nalLen =
((in.get() & 0xff) << 24) |
((in.get() & 0xff) << 16) |
((in.get() & 0xff) << 8) |
(in.get() & 0xff);
if (nalLen <= 0 || nalLen > in.remaining()) {
break;
}
out.put(START_CODE);
out.put(in.array(), in.position(), nalLen);
in.position(in.position() + nalLen);
}
byte[] annexB = new byte[out.position()];
out.flip();
out.get(annexB);
return annexB;
}
}

+ 46
- 0
src/main/java/com/inspect/tcpserver/sip/gb28181/MediaSession.java View File

@ -0,0 +1,46 @@
package com.inspect.tcpserver.sip.gb28181;
import java.net.*;
import java.util.concurrent.atomic.AtomicBoolean;
public class MediaSession {
private final String remoteIp;
private final int remotePort;
private final int ssrc;
private DatagramSocket socket;
private final AtomicBoolean running = new AtomicBoolean(false);
private final RtpSender rtpSender;
private final PsMuxer psMuxer;
public MediaSession(String ip, int port, int ssrc) throws Exception {
this.remoteIp = ip;
this.remotePort = port;
this.ssrc = ssrc;
this.socket = new DatagramSocket();
this.rtpSender = new RtpSender(socket, InetAddress.getByName(ip), port, ssrc);
this.psMuxer = new PsMuxer();
}
public void start() {
running.set(true);
}
public void stop() {
running.set(false);
socket.close();
}
// RTSP Client 回调此方法
public void onH264Frame(byte[] annexB, long pts90k) throws Exception {
if (!running.get()) return;
byte[] ps = psMuxer.muxH264(annexB, pts90k);
rtpSender.send(ps, true);
}
}

+ 28
- 0
src/main/java/com/inspect/tcpserver/sip/gb28181/MediaSessionEx.java View File

@ -0,0 +1,28 @@
package com.inspect.tcpserver.sip.gb28181;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
public class MediaSessionEx {
private final RtpSenderEx rtpSenderEx;
private final Gb28181PsMuxer psMuxer;
public MediaSessionEx(String remoteIp, int remotePort, int ssrc) throws Exception {
this.rtpSenderEx = new RtpSenderEx(remoteIp, remotePort, ssrc, 96);
this.psMuxer = new Gb28181PsMuxer();
}
/** RTSP Client 回调 */
public void onH264Frame(byte[] annexB, long pts90k) {
byte[] ps = psMuxer.muxOneFrame(annexB, pts90k);
if (ps != null) {
rtpSenderEx.sendOne(ps, pts90k);
}
}
public void close() {
rtpSenderEx.close();
}
}

+ 30
- 0
src/main/java/com/inspect/tcpserver/sip/gb28181/PesPacket.java View File

@ -0,0 +1,30 @@
package com.inspect.tcpserver.sip.gb28181;
import java.io.ByteArrayOutputStream;
public class PesPacket {
public static byte[] buildH264Pes(byte[] h264, long pts90k) throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
out.write(new byte[]{
0x00,0x00,0x01,(byte)0xE0,
0x00,0x00,
(byte)0x80,(byte)0x80,0x05
});
writePts(out, pts90k);
out.write(h264);
return out.toByteArray();
}
private static void writePts(ByteArrayOutputStream out, long pts) {
long val = pts & 0x1FFFFFFFFL;
out.write((byte)(0x21 | ((val >> 29) & 0x0E)));
out.write((byte)(val >> 22));
out.write((byte)(0x01 | ((val >> 14) & 0xFE)));
out.write((byte)(val >> 7));
out.write((byte)(0x01 | ((val << 1) & 0xFE)));
}
}

+ 35
- 0
src/main/java/com/inspect/tcpserver/sip/gb28181/PsHeader.java View File

@ -0,0 +1,35 @@
package com.inspect.tcpserver.sip.gb28181;
public class PsHeader {
public static byte[] packHeader() {
return new byte[]{
0x00,0x00,0x01,(byte)0xBA,
0x44,0x00,0x04,0x00,
0x04,0x01,(byte)0x89,(byte)0xC3,
(byte)0xF8
};
}
public static byte[] systemHeader() {
return new byte[]{
0x00,0x00,0x01,(byte)0xBB,
0x00,0x0C,
(byte)0x80,0x04,
(byte)0xE0,0x07,
(byte)0xE0,(byte)0xC0,
0x20,(byte)0xBD,(byte)0xE0
};
}
public static byte[] psmHeader() {
return new byte[]{
0x00,0x00,0x01,(byte)0xBC,
0x00,0x12,
0x00,0x00,
(byte)0xE0,0x00,
0x1B,0x00,0x00,
0x00,0x00
};
}
}

+ 21
- 0
src/main/java/com/inspect/tcpserver/sip/gb28181/PsMuxer.java View File

@ -0,0 +1,21 @@
package com.inspect.tcpserver.sip.gb28181;
import java.io.*;
public class PsMuxer {
private boolean sentHeader = false;
public byte[] muxH264(byte[] annexB, long pts90k) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
if (!sentHeader) {
out.write(PsPackHeader.packHeader());
out.write(PsSystemHeader.systemHeader());
out.write(PsProgramStreamMap.psm());
sentHeader = true;
}
out.write(PsPesPacket.pes(annexB, pts90k));
return out.toByteArray();
}
}

+ 10
- 0
src/main/java/com/inspect/tcpserver/sip/gb28181/PsPackHeader.java View File

@ -0,0 +1,10 @@
package com.inspect.tcpserver.sip.gb28181;
public class PsPackHeader {
public static byte[] packHeader() {
return new byte[]{
0x00,0x00,0x01,(byte)0xBA,
0x44,0x00,0x04,0x00,0x04,0x01,0x00,0x01
};
}
}

+ 28
- 0
src/main/java/com/inspect/tcpserver/sip/gb28181/PsPesPacket.java View File

@ -0,0 +1,28 @@
package com.inspect.tcpserver.sip.gb28181;
import java.io.*;
public class PsPesPacket {
public static byte[] pes(byte[] h264, long pts) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
out.write(new byte[]{0x00,0x00,0x01,(byte)0xE0});
int len = h264.length + 13;
out.write((len >> 8) & 0xFF);
out.write(len & 0xFF);
out.write(new byte[]{(byte)0x80,(byte)0x80,0x05});
writePts(out, pts);
out.write(h264);
return out.toByteArray();
}
private static void writePts(ByteArrayOutputStream out, long pts) throws IOException {
long val = pts & 0x1FFFFFFFFL;
out.write((byte)(0x21 | (((val >> 30) & 0x07) << 1)));
out.write((byte)((val >> 22) & 0xFF));
out.write((byte)((((val >> 15) & 0x7F) << 1) | 1));
out.write((byte)((val >> 7) & 0xFF));
out.write((byte)(((val & 0x7F) << 1) | 1));
}
}

+ 12
- 0
src/main/java/com/inspect/tcpserver/sip/gb28181/PsProgramStreamMap.java View File

@ -0,0 +1,12 @@
package com.inspect.tcpserver.sip.gb28181;
public class PsProgramStreamMap {
public static byte[] psm() {
return new byte[]{
0x00,0x00,0x01,(byte)0xBC,
0x00,0x12,
(byte)0xE0,0x00,0x00,0x00,
0x01,(byte)0xE0,0x00,0x00
};
}
}

+ 12
- 0
src/main/java/com/inspect/tcpserver/sip/gb28181/PsSystemHeader.java View File

@ -0,0 +1,12 @@
package com.inspect.tcpserver.sip.gb28181;
public class PsSystemHeader {
public static byte[] systemHeader() {
return new byte[]{
0x00,0x00,0x01,(byte)0xBB,
0x00,0x0C,
(byte)0x80,(byte)0x04,(byte)0xE4,(byte)0x7F,
(byte)0xE0,0x07,(byte)0xE1,0x07
};
}
}

+ 60
- 0
src/main/java/com/inspect/tcpserver/sip/gb28181/RtpSender.java View File

@ -0,0 +1,60 @@
package com.inspect.tcpserver.sip.gb28181;
import java.net.*;
import java.util.concurrent.atomic.AtomicInteger;
public class RtpSender {
private static final int RTP_HEADER_SIZE = 12;
private static final int MTU = 1400;
private final DatagramSocket socket;
private final InetAddress remote;
private final int port;
private final int ssrc;
private final AtomicInteger seq = new AtomicInteger(0);
private int timestamp = 0;
public RtpSender(DatagramSocket socket, InetAddress remote, int port, int ssrc) {
this.socket = socket;
this.remote = remote;
this.port = port;
this.ssrc = ssrc;
}
public void send(byte[] ps, boolean marker) throws Exception {
int offset = 0;
while (offset < ps.length) {
int size = Math.min(MTU, ps.length - offset);
byte[] pkt = new byte[RTP_HEADER_SIZE + size];
pkt[0] = (byte) 0x80;
pkt[1] = (byte) (96 | (marker ? 0x80 : 0x00));
pkt[2] = (byte) (seq.get() >> 8);
pkt[3] = (byte) (seq.getAndIncrement());
timestamp += 3600;
pkt[4] = (byte) (timestamp >> 24);
pkt[5] = (byte) (timestamp >> 16);
pkt[6] = (byte) (timestamp >> 8);
pkt[7] = (byte) (timestamp);
pkt[8] = (byte) (ssrc >> 24);
pkt[9] = (byte) (ssrc >> 16);
pkt[10] = (byte) (ssrc >> 8);
pkt[11] = (byte) (ssrc);
System.arraycopy(ps, offset, pkt, RTP_HEADER_SIZE, size);
socket.send(new DatagramPacket(pkt, pkt.length, remote, port));
offset += size;
}
}
}

+ 84
- 0
src/main/java/com/inspect/tcpserver/sip/gb28181/RtpSenderEx.java View File

@ -0,0 +1,84 @@
package com.inspect.tcpserver.sip.gb28181;
import java.net.*;
import java.util.concurrent.atomic.AtomicInteger;
public class RtpSenderEx {
private final DatagramSocket socket;
private final InetSocketAddress target;
private final int ssrc;
private final int payloadType;
private final AtomicInteger seq = new AtomicInteger(0);
public RtpSenderEx(String ip, int port, int ssrc, int pt) throws Exception {
this.socket = new DatagramSocket();
this.target = new InetSocketAddress(ip, port);
this.ssrc = ssrc;
this.payloadType = pt;
}
// public void send(byte[] ps, long ts90k) {
// int offset = 0;
// while (offset < ps.length) {
// int len = Math.min(1400, ps.length - offset);
// byte[] rtp = buildRtp(ps, offset, len, ts90k);
// socketSend(rtp);
// offset += len;
// }
// }
//
// private byte[] buildRtp(byte[] ps, int off, int len, long ts) {
// byte[] pkt = new byte[12 + len];
// pkt[0] = (byte) 0x80;
// pkt[1] = (byte) payloadType;
// pkt[2] = (byte) (seq.getAndIncrement() >> 8);
// pkt[3] = (byte) seq.get();
// pkt[4] = (byte) (ts >> 24);
// pkt[5] = (byte) (ts >> 16);
// pkt[6] = (byte) (ts >> 8);
// pkt[7] = (byte) ts;
// pkt[8] = (byte) (ssrc >> 24);
// pkt[9] = (byte) (ssrc >> 16);
// pkt[10] = (byte) (ssrc >> 8);
// pkt[11] = (byte) ssrc;
//
// System.arraycopy(ps, off, pkt, 12, len);
// return pkt;
// }
public void sendOne(byte[] ps, long ts90k) {
byte[] rtp = buildRtp(ps, ts90k, true);
socketSend(rtp);
}
private byte[] buildRtp(byte[] payload, long ts, boolean marker) {
byte[] pkt = new byte[12 + payload.length];
pkt[0] = (byte) 0x80;
pkt[1] = (byte) (payloadType | (marker ? 0x80 : 0));
pkt[2] = (byte) (seq.get() >> 8);
pkt[3] = (byte) seq.getAndIncrement();
pkt[4] = (byte) (ts >> 24);
pkt[5] = (byte) (ts >> 16);
pkt[6] = (byte) (ts >> 8);
pkt[7] = (byte) ts;
pkt[8] = (byte) (ssrc >> 24);
pkt[9] = (byte) (ssrc >> 16);
pkt[10] = (byte) (ssrc >> 8);
pkt[11] = (byte) ssrc;
System.arraycopy(payload, 0, pkt, 12, payload.length);
return pkt;
}
private void socketSend(byte[] pkt) {
try {
socket.send(new DatagramPacket(pkt, pkt.length, target));
} catch (Exception ignored) {}
}
public void close() {
socket.close();
}
}

+ 74
- 0
src/main/java/com/inspect/tcpserver/sip/gb28181/RtspClient.java View File

@ -0,0 +1,74 @@
package com.inspect.tcpserver.sip.gb28181;
import org.bytedeco.ffmpeg.avcodec.AVPacket;
import org.bytedeco.javacv.FFmpegFrameGrabber;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.BiConsumer;
public class RtspClient {
private final String url;
private FFmpegFrameGrabber grabber;
private final AtomicBoolean running = new AtomicBoolean(false);
public RtspClient(String url) {
this.url = url;
}
/**
* @param onH264 callback(h264AnnexB, pts90k)
*/
public void start(BiConsumer<byte[], Long> onH264) throws Exception {
grabber = new FFmpegFrameGrabber(url);
grabber.setOption("rtsp_transport", "tcp");
grabber.setOption("stimeout", "5000000");
grabber.start();
running.set(true);
new Thread(() -> {
try {
while (running.get()) {
AVPacket pkt = grabber.grabPacket();
if (pkt == null || pkt.size() <= 0) {
continue;
}
// 1️ 提取 Annex-B H264
byte[] annexB = H264AnnexBExtractor.extractFromAvPacket(pkt);
if (annexB == null) {
continue;
}
// 2️ PTS 90kHz
long pts90k = ptsTo90k(pkt.pts(), grabber.getTimestamp());
onH264.accept(annexB, pts90k);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
stop();
}
}, "RtspClient-Thread").start();
}
private long ptsTo90k(long pts, double timeBase) {
if (pts <= 0 || timeBase <= 0) {
return System.nanoTime() / 11_111; // fallback
}
return (long) (pts * timeBase * 90_000);
}
public void stop() {
running.set(false);
try {
if (grabber != null) {
grabber.stop();
grabber.release();
}
} catch (Exception ignored) {
}
}
}

+ 45
- 0
src/main/java/com/inspect/tcpserver/sip/media/MediaSession.java View File

@ -0,0 +1,45 @@
package com.inspect.tcpserver.sip.media;
import com.inspect.tcpserver.sip.media.rtcp.RtcpSrSender;
import com.inspect.tcpserver.sip.media.rtp.RtpPacketizer;
import com.inspect.tcpserver.sip.media.rtp.RtpSender;
import com.inspect.tcpserver.sip.media.rtp.RtpTcpSender;
import com.inspect.tcpserver.sip.media.rtp.RtpUdpSender;
import com.inspect.tcpserver.sip.media.rtsp.RtspClient;
public class MediaSession {
private final String remoteIp;
private final int remotePort;
private final long ssrc;
private final boolean tcp;
private final String rtspUrl;
public MediaSession(String remoteIp, int remotePort, long ssrc, boolean tcp, String rtspUrl) {
this.remoteIp = remoteIp;
this.remotePort = remotePort;
this.ssrc = ssrc;
this.tcp = tcp;
this.rtspUrl = rtspUrl;
}
public void start() throws Exception {
RtpSender sender = tcp
? new RtpTcpSender(remoteIp, remotePort)
: new RtpUdpSender(remoteIp, remotePort);
// 先发 RTCP SRB.6.1
RtcpSrSender.send(sender.getControlStream(), ssrc);
// 启动 RTSP 拉流 RTP
RtspClient rtspClient = new RtspClient(rtspUrl);
rtspClient.start(nal -> {
try {
RtpPacketizer.packetizerAndSend(nal, ssrc, sender);
} catch (Exception e) {
e.printStackTrace();
}
});
}
}

+ 138
- 0
src/main/java/com/inspect/tcpserver/sip/media/SdpInfo.java View File

@ -0,0 +1,138 @@
package com.inspect.tcpserver.sip.media;
import java.util.HashMap;
import java.util.Map;
public class SdpInfo {
/** 会话级 IP(c= 行) */
private String remoteIp;
/** 媒体端口(m=video) */
private int mediaPort;
/** RTP / TCP / UDP */
private Transport transport;
/** payload type,如 98 */
private int payloadType;
/** 编码,如 H264 */
private String codec;
/** 时钟频率,如 90000 */
private int clockRate;
/** SDP setup 属性(RFC4145) */
private String setup; // active / passive
/** SDP connection 属性 */
private String connection; // new
/** y= 行里的 SSRC */
private long ssrc;
/** 原始属性缓存(方便扩展) */
private final Map<String, String> attributes = new HashMap<>();
public enum Transport {
RTP_UDP,
RTP_TCP
}
public String getRemoteIp() {
return remoteIp;
}
public void setRemoteIp(String remoteIp) {
this.remoteIp = remoteIp;
}
public int getMediaPort() {
return mediaPort;
}
public void setMediaPort(int mediaPort) {
this.mediaPort = mediaPort;
}
public Transport getTransport() {
return transport;
}
public void setTransport(Transport transport) {
this.transport = transport;
}
public int getPayloadType() {
return payloadType;
}
public void setPayloadType(int payloadType) {
this.payloadType = payloadType;
}
public String getCodec() {
return codec;
}
public void setCodec(String codec) {
this.codec = codec;
}
public int getClockRate() {
return clockRate;
}
public void setClockRate(int clockRate) {
this.clockRate = clockRate;
}
public String getSetup() {
return setup;
}
public void setSetup(String setup) {
this.setup = setup;
}
public String getConnection() {
return connection;
}
public void setConnection(String connection) {
this.connection = connection;
}
public long getSsrc() {
return ssrc;
}
public void setSsrc(long ssrc) {
this.ssrc = ssrc;
}
public void putAttribute(String key, String value) {
attributes.put(key, value);
}
public String getAttribute(String key) {
return attributes.get(key);
}
@Override
public String toString() {
return "SdpInfo{" +
"remoteIp='" + remoteIp + '\'' +
", mediaPort=" + mediaPort +
", transport=" + transport +
", payloadType=" + payloadType +
", codec='" + codec + '\'' +
", clockRate=" + clockRate +
", setup='" + setup + '\'' +
", connection='" + connection + '\'' +
", ssrc=" + ssrc +
'}';
}
}

+ 92
- 0
src/main/java/com/inspect/tcpserver/sip/media/SdpParser.java View File

@ -0,0 +1,92 @@
package com.inspect.tcpserver.sip.media;
import java.util.StringTokenizer;
public class SdpParser {
public static SdpInfo parse(String sdp) {
SdpInfo info = new SdpInfo();
String[] lines = sdp.split("\\r?\\n");
for (String line : lines) {
line = line.trim();
if (line.isEmpty()) continue;
// ---------- 会话级 ----------
if (line.startsWith("c=")) {
// c=IN IP4 192.168.1.116
String[] parts = line.split("\\s+");
if (parts.length >= 3) {
info.setRemoteIp(parts[2]);
}
}
// ---------- media ----------
else if (line.startsWith("m=video")) {
// m=video 50000 TCP/AVP 98
StringTokenizer st = new StringTokenizer(line);
st.nextToken(); // m=video
info.setMediaPort(Integer.parseInt(st.nextToken()));
String proto = st.nextToken();
if (proto.toUpperCase().contains("TCP")) {
info.setTransport(SdpInfo.Transport.RTP_TCP);
} else {
info.setTransport(SdpInfo.Transport.RTP_UDP);
}
info.setPayloadType(Integer.parseInt(st.nextToken()));
}
// ---------- rtpmap ----------
else if (line.startsWith("a=rtpmap")) {
// a=rtpmap:98 H264/90000
String value = line.substring("a=rtpmap:".length());
String[] parts = value.split("\\s+");
if (parts.length == 2) {
String[] codecInfo = parts[1].split("/");
info.setCodec(codecInfo[0]);
info.setClockRate(Integer.parseInt(codecInfo[1]));
}
}
// ---------- setup ----------
else if (line.startsWith("a=setup:")) {
info.setSetup(line.substring("a=setup:".length()));
}
// ---------- connection ----------
else if (line.startsWith("a=connection:")) {
info.setConnection(line.substring("a=connection:".length()));
}
// ---------- fmtp ----------
else if (line.startsWith("a=fmtp:")) {
// 可留作扩展
info.putAttribute("fmtp", line);
}
// ---------- y= (SSRC) ----------
else if (line.startsWith("y=")) {
// y=0700000038
info.setSsrc(Long.parseLong(line.substring(2)));
}
// ---------- 其他属性 ----------
else if (line.startsWith("a=")) {
int idx = line.indexOf(':');
if (idx > 0) {
info.putAttribute(
line.substring(2, idx),
line.substring(idx + 1)
);
}
}
}
return info;
}
}

+ 24
- 0
src/main/java/com/inspect/tcpserver/sip/media/rtcp/RtcpPacket.java View File

@ -0,0 +1,24 @@
package com.inspect.tcpserver.sip.media.rtcp;
import java.nio.ByteBuffer;
public class RtcpPacket {
public static byte[] buildSr(long ssrc) {
ByteBuffer buf = ByteBuffer.allocate(28);
buf.put((byte) 0x80);
buf.put((byte) 200);
buf.putShort((short) 6);
buf.putInt((int) ssrc);
long ntp = System.currentTimeMillis() * 2208988800L;
buf.putLong(ntp);
buf.putInt(0);
buf.putInt(0);
buf.putInt(0);
return buf.array();
}
}

+ 13
- 0
src/main/java/com/inspect/tcpserver/sip/media/rtcp/RtcpSrSender.java View File

@ -0,0 +1,13 @@
package com.inspect.tcpserver.sip.media.rtcp;
import java.io.OutputStream;
public class RtcpSrSender {
public static void send(OutputStream out, long ssrc) throws Exception {
byte[] sr = RtcpPacket.buildSr(ssrc);
out.write(sr);
out.flush();
}
}

+ 37
- 0
src/main/java/com/inspect/tcpserver/sip/media/rtp/RtpPacket.java View File

@ -0,0 +1,37 @@
package com.inspect.tcpserver.sip.media.rtp;
public class RtpPacket {
public static byte[] build(
int payloadType,
int seq,
int timestamp,
long ssrc,
byte[] payload,
boolean marker
) {
int headerSize = 12;
byte[] packet = new byte[headerSize + payload.length];
packet[0] = (byte) 0x80;
packet[1] = (byte) (payloadType & 0x7F);
if (marker) {
packet[1] |= 0x80;
}
packet[2] = (byte) (seq >> 8);
packet[3] = (byte) seq;
packet[4] = (byte) (timestamp >> 24);
packet[5] = (byte) (timestamp >> 16);
packet[6] = (byte) (timestamp >> 8);
packet[7] = (byte) timestamp;
packet[8] = (byte) (ssrc >> 24);
packet[9] = (byte) (ssrc >> 16);
packet[10] = (byte) (ssrc >> 8);
packet[11] = (byte) ssrc;
System.arraycopy(payload, 0, packet, headerSize, payload.length);
return packet;
}
}

+ 48
- 0
src/main/java/com/inspect/tcpserver/sip/media/rtp/RtpPacketizer.java View File

@ -0,0 +1,48 @@
package com.inspect.tcpserver.sip.media.rtp;
public class RtpPacketizer {
private static final int MTU = 1400;
private static int seq = 0;
private static int timestamp = 0;
public static void packetizerAndSend(byte[] nal, long ssrc, RtpSender sender) throws Exception {
if (nal.length <= MTU) {
byte[] pkt = RtpPacket.build(
98, seq++, timestamp, ssrc, nal, true
);
sender.send(pkt, pkt.length);
} else {
// FU-A
int nalHeader = nal[0] & 0xFF;
int fuIndicator = (nalHeader & 0xE0) | 28;
int fuHeaderStart = 0x80 | (nalHeader & 0x1F);
int fuHeaderMid = nalHeader & 0x1F;
int fuHeaderEnd = 0x40 | (nalHeader & 0x1F);
int offset = 1;
boolean start = true;
while (offset < nal.length) {
int size = Math.min(MTU - 2, nal.length - offset);
byte[] payload = new byte[size + 2];
payload[0] = (byte) fuIndicator;
payload[1] = (byte) (start ? fuHeaderStart :
(offset + size >= nal.length ? fuHeaderEnd : fuHeaderMid));
System.arraycopy(nal, offset, payload, 2, size);
offset += size;
start = false;
byte[] pkt = RtpPacket.build(
98, seq++, timestamp, ssrc, payload, offset >= nal.length
);
sender.send(pkt, pkt.length);
}
}
timestamp += 3600;
}
}

+ 11
- 0
src/main/java/com/inspect/tcpserver/sip/media/rtp/RtpSender.java View File

@ -0,0 +1,11 @@
package com.inspect.tcpserver.sip.media.rtp;
import java.io.OutputStream;
public interface RtpSender {
void send(byte[] data, int len) throws Exception;
OutputStream getControlStream();
}

+ 31
- 0
src/main/java/com/inspect/tcpserver/sip/media/rtp/RtpTcpSender.java View File

@ -0,0 +1,31 @@
package com.inspect.tcpserver.sip.media.rtp;
import java.io.OutputStream;
import java.net.Socket;
import java.nio.ByteBuffer;
public class RtpTcpSender implements RtpSender {
private final Socket socket;
private final OutputStream out;
public RtpTcpSender(String ip, int port) throws Exception {
this.socket = new Socket(ip, port);
this.out = socket.getOutputStream();
}
@Override
public void send(byte[] data, int len) throws Exception {
ByteBuffer buf = ByteBuffer.allocate(len + 2);
buf.putShort((short) len);
buf.put(data, 0, len);
out.write(buf.array());
out.flush();
}
@Override
public OutputStream getControlStream() {
return out;
}
}

+ 32
- 0
src/main/java/com/inspect/tcpserver/sip/media/rtp/RtpUdpSender.java View File

@ -0,0 +1,32 @@
package com.inspect.tcpserver.sip.media.rtp;
import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
public class RtpUdpSender implements RtpSender {
private final DatagramSocket socket;
private final InetAddress addr;
private final int port;
public RtpUdpSender(String ip, int port) throws Exception {
this.socket = new DatagramSocket();
this.addr = InetAddress.getByName(ip);
this.port = port;
}
@Override
public void send(byte[] data, int len) throws Exception {
DatagramPacket packet = new DatagramPacket(data, len, addr, port);
socket.send(packet);
}
@Override
public OutputStream getControlStream() {
return new ByteArrayOutputStream(); // UDP 场景可忽略
}
}

+ 21
- 0
src/main/java/com/inspect/tcpserver/sip/media/rtsp/H264AnnexBExtractor.java View File

@ -0,0 +1,21 @@
package com.inspect.tcpserver.sip.media.rtsp;
import org.bytedeco.javacv.Frame;
import java.nio.ByteBuffer;
public class H264AnnexBExtractor {
public static byte[] extract(Frame frame) {
if (frame.image == null || frame.image.length == 0) {
return null;
}
ByteBuffer buffer = (ByteBuffer) frame.image[0];
byte[] data = new byte[buffer.remaining()];
buffer.get(data);
return data;
}
}

+ 33
- 0
src/main/java/com/inspect/tcpserver/sip/media/rtsp/RtspClient.java View File

@ -0,0 +1,33 @@
package com.inspect.tcpserver.sip.media.rtsp;
import org.bytedeco.javacv.FFmpegFrameGrabber;
import org.bytedeco.javacv.Frame;
import java.util.function.Consumer;
public class RtspClient {
private final String url;
public RtspClient(String url) {
this.url = url;
}
public void start(Consumer<byte[]> onNal) throws Exception {
FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(url);
grabber.setOption("rtsp_transport", "tcp");
grabber.start();
while (true) {
Frame frame = grabber.grabImage();
if (frame == null || frame.image == null) {
continue;
}
byte[] annexB = H264AnnexBExtractor.extract(frame);
if (annexB != null) {
onNal.accept(annexB);
}
}
}
}

+ 96
- 23
src/main/java/com/inspect/tcpserver/sip/service/SipClientService.java View File

@ -1,6 +1,12 @@
package com.inspect.tcpserver.sip.service; package com.inspect.tcpserver.sip.service;
import com.inspect.tcpserver.sip.gb28181.MediaSession;
import com.inspect.tcpserver.sip.gb28181.MediaSessionEx;
import com.inspect.tcpserver.sip.gb28181.RtspClient;
import com.inspect.tcpserver.sip.registry.SipEventRegistry; import com.inspect.tcpserver.sip.registry.SipEventRegistry;
import com.inspect.tcpserver.sip.utils.DigestUtil; import com.inspect.tcpserver.sip.utils.DigestUtil;
import com.inspect.tcpserver.sip.utils.SipXmlEnvelope; import com.inspect.tcpserver.sip.utils.SipXmlEnvelope;
import com.inspect.tcpserver.sip.utils.SipXmlParser; import com.inspect.tcpserver.sip.utils.SipXmlParser;
@ -20,7 +26,7 @@ import javax.sip.message.MessageFactory;
import javax.sip.message.Request; import javax.sip.message.Request;
import javax.sip.message.Response; import javax.sip.message.Response;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.net.*; import java.net.*;
@ -833,7 +839,10 @@ public class SipClientService implements SipListener {
} else if(Request.ACK.equals(request.getMethod())) { } else if(Request.ACK.equals(request.getMethod())) {
// RFC 3261: The ACK request does not generate a response. // RFC 3261: The ACK request does not generate a response.
// ACK 事务内请求不是普通 SIP 方法,不生成任何 Response,ACK 事务内请求不是普通 SIP 方法 // ACK 事务内请求不是普通 SIP 方法,不生成任何 Response,ACK 事务内请求不是普通 SIP 方法
startRtspToRtp();
//Thread.sleep(30);
log.info("CALL startRtspToRtp");
//startRtspToRtp();
} }
else { else {
Response notImpl = messageFactory.createResponse(Response.NOT_IMPLEMENTED, request); Response notImpl = messageFactory.createResponse(Response.NOT_IMPLEMENTED, request);
@ -853,32 +862,96 @@ public class SipClientService implements SipListener {
private Process ffmpegProcess; private Process ffmpegProcess;
// private void startRtspToRtp() {
// try {
// String cmd =
// "ffmpeg -rtsp_transport tcp " +
// "-i \"rtsp://admin:wd19216811@192.168.1.244:554/h264/ch1/sub/av_stream\" " +
// "-an -vcodec copy " +
// "-f rtp -payload_type 96 " +
// "\"rtp://192.168.1.116:50000?tcp\"";
//
// ProcessBuilder pb = new ProcessBuilder(
// "bash", "-c", cmd
// );
// pb.redirectErrorStream(true);
//
// ffmpegProcess = pb.start();
//
// new Thread(() -> {
// try (BufferedReader br =
// new BufferedReader(new InputStreamReader(
// ffmpegProcess.getInputStream()))) {
// String line;
// while ((line = br.readLine()) != null) {
// System.out.println("[FFmpeg] " + line);
// }
// } catch (IOException ignored) {}
// }).start();
//
// } catch (Exception e) {
// e.printStackTrace();
// }
//
// try {
// MediaSession session = new MediaSession(
// "192.168.1.116",
// 50000,
// 1,
// false,
// "rtsp://admin:wd19216811@192.168.1.244:554/h264/ch1/sub/av_stream"
// );
// } catch (Exception e) {
//
// }
//
//
// }
// private void startRtspToRtp() {
// try {
// MediaSession session = new MediaSession(
// "192.168.1.116",
// 50000,
// 1
// );
//
// session.startRtsp(
// "rtsp://admin:wd19216811@192.168.1.244:554/h264/ch1/sub/av_stream"
// );
//
// } catch (Exception e) {
// e.printStackTrace();
// }
// }
private void startRtspToRtp() { private void startRtspToRtp() {
try { try {
String cmd =
"ffmpeg -rtsp_transport tcp " +
"-i \"rtsp://admin:wd19216811@192.168.1.244:554/h264/ch1/sub/av_stream\" " +
"-an -vcodec copy " +
"-f rtp -payload_type 96 " +
"\"rtp://192.168.1.116:50000?tcp\"";
ProcessBuilder pb = new ProcessBuilder(
"bash", "-c", cmd
MediaSessionEx session = new MediaSessionEx(
"192.168.1.116",
50000,
1
); );
pb.redirectErrorStream(true);
//session.start();
ffmpegProcess = pb.start();
RtspClient client = new RtspClient(
"rtsp://admin:wd19216811@192.168.1.244:554/h264/ch1/sub/av_stream"
);
new Thread(() -> {
try (BufferedReader br =
new BufferedReader(new InputStreamReader(
ffmpegProcess.getInputStream()))) {
String line;
while ((line = br.readLine()) != null) {
System.out.println("[FFmpeg] " + line);
}
} catch (IOException ignored) {}
}).start();
// client.start((h264, pts90k) -> {
// try {
// session.onH264Frame(h264, pts90k);
// } catch (Exception e) {
// e.printStackTrace();
// }
// });
client.start((h264, pts90k) -> {
try {
session.onH264Frame(h264, pts90k);
} catch (Exception e) {
e.printStackTrace();
}
});
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();


+ 35
- 0
src/main/java/com/inspect/tcpserver/sip/stream/H264AnnexBExtractor.java View File

@ -0,0 +1,35 @@
package com.inspect.tcpserver.sip.stream;
import org.bytedeco.ffmpeg.avcodec.AVPacket;
import org.bytedeco.javacpp.BytePointer;
public class H264AnnexBExtractor {
private static final byte[] START_CODE = {0x00, 0x00, 0x00, 0x01};
public static byte[] extractFromAvPacket(AVPacket pkt) {
BytePointer dataPtr = pkt.data();
int size = pkt.size();
if (dataPtr == null || size <= 0) {
return null;
}
byte[] raw = new byte[size];
dataPtr.get(raw);
// 如果已经是 Annex-B00 00 00 01
if (raw.length >= 4 &&
raw[0] == 0x00 && raw[1] == 0x00 &&
raw[2] == 0x00 && raw[3] == 0x01) {
return raw;
}
// AVCC Annex-B简单处理前置 start code
byte[] annexB = new byte[START_CODE.length + raw.length];
System.arraycopy(START_CODE, 0, annexB, 0, START_CODE.length);
System.arraycopy(raw, 0, annexB, START_CODE.length, raw.length);
return annexB;
}
}

+ 35
- 0
src/main/java/com/inspect/tcpserver/sip/stream/MediaSession.java View File

@ -0,0 +1,35 @@
package com.inspect.tcpserver.sip.stream;
import com.inspect.tcpserver.sip.stream.ps.PsMuxer;
public class MediaSession {
private final PsMuxer psMuxer;
private final RtpSender rtpSender;
private RtspClient rtspClient;
public MediaSession(String remoteIp, int remotePort, int ssrc) throws Exception {
this.psMuxer = new PsMuxer();
this.rtpSender = new RtpSender(remoteIp, remotePort, ssrc);
}
public void startRtsp(String rtspUrl) throws Exception {
rtspClient = new RtspClient(rtspUrl);
rtspClient.start(h264 -> {
try {
byte[] ps = psMuxer.mux(h264);
rtpSender.sendPs(ps);
} catch (Exception e) {
e.printStackTrace();
}
});
}
public void stop() {
if (rtspClient != null) {
rtspClient.stop();
}
rtpSender.close();
}
}

+ 15
- 0
src/main/java/com/inspect/tcpserver/sip/stream/RtpClock.java View File

@ -0,0 +1,15 @@
package com.inspect.tcpserver.sip.stream;
public class RtpClock {
private static final long CLOCK_90K = 90000;
private static final long FRAME_INTERVAL = 3600; // 40ms
private long timestamp = 0;
public long next() {
timestamp += FRAME_INTERVAL;
return timestamp;
}
}

+ 39
- 0
src/main/java/com/inspect/tcpserver/sip/stream/RtpPacket.java View File

@ -0,0 +1,39 @@
package com.inspect.tcpserver.sip.stream;
public class RtpPacket {
public static final int HEADER_SIZE = 12;
public byte[] buffer;
public int length;
public RtpPacket(int payloadSize) {
buffer = new byte[HEADER_SIZE + payloadSize];
}
public void setHeader(
int payloadType,
int seq,
long timestamp,
int ssrc,
boolean marker
) {
buffer[0] = (byte) 0x80;
buffer[1] = (byte) (payloadType & 0x7F);
if (marker) buffer[1] |= 0x80;
buffer[2] = (byte) (seq >> 8);
buffer[3] = (byte) (seq);
buffer[4] = (byte) (timestamp >> 24);
buffer[5] = (byte) (timestamp >> 16);
buffer[6] = (byte) (timestamp >> 8);
buffer[7] = (byte) (timestamp);
buffer[8] = (byte) (ssrc >> 24);
buffer[9] = (byte) (ssrc >> 16);
buffer[10] = (byte) (ssrc >> 8);
buffer[11] = (byte) (ssrc);
}
}

+ 46
- 0
src/main/java/com/inspect/tcpserver/sip/stream/RtpSender.java View File

@ -0,0 +1,46 @@
package com.inspect.tcpserver.sip.stream;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class RtpSender {
private static final int MTU = 1400;
private static final int PAYLOAD_TYPE = 98;
private int seq = 0;
private final int ssrc;
private final RtpSocket socket;
private final RtpClock clock = new RtpClock();
public RtpSender(String ip, int port, int ssrc) throws Exception {
this.ssrc = ssrc;
this.socket = new RtpSocket(ip, port);
}
public void sendPs(byte[] ps) throws Exception {
long ts = clock.next();
int offset = 0;
while (offset < ps.length) {
int size = Math.min(MTU, ps.length - offset);
boolean marker = (offset + size) >= ps.length;
RtpPacket pkt = new RtpPacket(size);
pkt.setHeader(PAYLOAD_TYPE, seq++, ts, ssrc, marker);
System.arraycopy(ps, offset, pkt.buffer, RtpPacket.HEADER_SIZE, size);
pkt.length = RtpPacket.HEADER_SIZE + size;
socket.send(pkt.buffer, pkt.length);
offset += size;
log.info("sendPs offset = {}", offset);
}
}
public void close() {
socket.close();
}
}

+ 26
- 0
src/main/java/com/inspect/tcpserver/sip/stream/RtpSocket.java View File

@ -0,0 +1,26 @@
package com.inspect.tcpserver.sip.stream;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
public class RtpSocket {
private final DatagramSocket socket;
private final InetSocketAddress remote;
public RtpSocket(String ip, int port) throws Exception {
socket = new DatagramSocket();
remote = new InetSocketAddress(ip, port);
}
public void send(byte[] data, int len) throws Exception {
DatagramPacket packet = new DatagramPacket(data, len, remote);
socket.send(packet);
}
public void close() {
socket.close();
}
}

+ 59
- 0
src/main/java/com/inspect/tcpserver/sip/stream/RtspClient.java View File

@ -0,0 +1,59 @@
package com.inspect.tcpserver.sip.stream;
import org.bytedeco.ffmpeg.avcodec.AVPacket;
import org.bytedeco.javacv.FFmpegFrameGrabber;
import java.nio.ByteBuffer;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
public class RtspClient {
private final String url;
private FFmpegFrameGrabber grabber;
private final AtomicBoolean running = new AtomicBoolean(false);
public RtspClient(String url) {
this.url = url;
}
public void start(Consumer<byte[]> onH264) throws Exception {
grabber = new FFmpegFrameGrabber(url);
grabber.setOption("rtsp_transport", "tcp");
grabber.setOption("stimeout", "5000000");
grabber.start();
running.set(true);
new Thread(() -> {
try {
while (running.get()) {
AVPacket pkt = grabber.grabPacket();
if (pkt == null || pkt.size() <= 0) {
continue;
}
byte[] annexB = H264AnnexBExtractor.extractFromAvPacket(pkt);
if (annexB != null) {
onH264.accept(annexB);
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
stop();
}
}, "RtspClient-Thread").start();
}
public void stop() {
running.set(false);
try {
if (grabber != null) {
grabber.stop();
grabber.release();
}
} catch (Exception ignored) {
}
}
}

+ 15
- 0
src/main/java/com/inspect/tcpserver/sip/stream/ps/PsConstants.java View File

@ -0,0 +1,15 @@
package com.inspect.tcpserver.sip.stream.ps;
public final class PsConstants {
public static final int PACK_START_CODE = 0x000001BA;
public static final int SYSTEM_HEADER_START_CODE = 0x000001BB;
public static final int PROGRAM_STREAM_MAP = 0x000001BC;
public static final int PES_VIDEO_START_CODE = 0x000001E0;
public static final int STREAM_ID_VIDEO = 0xE0;
public static final long CLOCK_90K = 90000L;
private PsConstants() {}
}

+ 28
- 0
src/main/java/com/inspect/tcpserver/sip/stream/ps/PsMuxer.java View File

@ -0,0 +1,28 @@
package com.inspect.tcpserver.sip.stream.ps;
import java.io.ByteArrayOutputStream;
public class PsMuxer {
private boolean headerSent = false;
private long scr = 0;
public byte[] mux(byte[] h264) throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
scr += 3600;
if (!headerSent) {
out.write(PsPackHeader.create(scr));
out.write(PsSystemHeader.create());
out.write(PsProgramStreamMap.create());
headerSent = true;
}
out.write(PsPackHeader.create(scr));
out.write(PsPesPacket.create(h264, scr));
return out.toByteArray();
}
}

+ 27
- 0
src/main/java/com/inspect/tcpserver/sip/stream/ps/PsPackHeader.java View File

@ -0,0 +1,27 @@
package com.inspect.tcpserver.sip.stream.ps;
import java.io.ByteArrayOutputStream;
public class PsPackHeader {
public static byte[] create(long scr) throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
out.write(new byte[]{0, 0, 1, (byte) 0xBA});
long b = scr & 0x1FFFFFFFFL;
out.write((byte) (0x44 | ((b >> 27) & 0x38)));
out.write((byte) (b >> 19));
out.write((byte) (0x04 | ((b >> 14) & 0xF8)));
out.write((byte) (b >> 6));
out.write((byte) (0x04 | ((b << 2) & 0xF8)));
out.write(0x01);
out.write(new byte[]{0x00, 0x01, (byte) 0x89, (byte) 0xC3});
out.write(0xF8);
return out.toByteArray();
}
}

+ 35
- 0
src/main/java/com/inspect/tcpserver/sip/stream/ps/PsPesPacket.java View File

@ -0,0 +1,35 @@
package com.inspect.tcpserver.sip.stream.ps;
import java.io.ByteArrayOutputStream;
public class PsPesPacket {
public static byte[] create(byte[] es, long pts) throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
out.write(new byte[]{0, 0, 1, (byte) 0xE0});
int len = es.length + 8;
out.write((len >> 8) & 0xFF);
out.write(len & 0xFF);
out.write(0x80);
out.write(0x80);
out.write(0x05);
writePts(out, pts);
out.write(es);
return out.toByteArray();
}
private static void writePts(ByteArrayOutputStream out, long pts) {
long v = pts & 0x1FFFFFFFFL;
out.write((byte) (0x21 | ((v >> 29) & 0x0E)));
out.write((byte) (v >> 22));
out.write((byte) (0x01 | ((v >> 14) & 0xFE)));
out.write((byte) (v >> 7));
out.write((byte) (0x01 | ((v << 1) & 0xFE)));
}
}

+ 26
- 0
src/main/java/com/inspect/tcpserver/sip/stream/ps/PsProgramStreamMap.java View File

@ -0,0 +1,26 @@
package com.inspect.tcpserver.sip.stream.ps;
import java.io.ByteArrayOutputStream;
public class PsProgramStreamMap {
public static byte[] create() throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
out.write(new byte[]{0, 0, 1, (byte) 0xBC});
out.write(new byte[]{0x00, 0x12});
out.write(0xE0);
out.write(new byte[]{0x00, 0x00});
out.write(new byte[]{0x00, 0x08});
out.write(0x1B); // H264
out.write(PsConstants.STREAM_ID_VIDEO);
out.write(new byte[]{0x00, 0x00});
out.write(new byte[]{0, 0, 0, 0}); // CRC
return out.toByteArray();
}
}

+ 21
- 0
src/main/java/com/inspect/tcpserver/sip/stream/ps/PsSystemHeader.java View File

@ -0,0 +1,21 @@
package com.inspect.tcpserver.sip.stream.ps;
import java.io.ByteArrayOutputStream;
public class PsSystemHeader {
public static byte[] create() throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
out.write(new byte[]{0, 0, 1, (byte) 0xBB});
out.write(new byte[]{0x00, 0x0C});
out.write(new byte[]{(byte) 0x80, 0x04, (byte) 0xE4});
out.write(0x04);
out.write(0xE1);
out.write(new byte[]{0x7F, (byte) 0xE0, 0x7F});
return out.toByteArray();
}
}

+ 1
- 1
src/main/resources/application-dev.yml View File

@ -52,7 +52,7 @@ sip:
domain: 192.168.1.116 # 平台SIP服务IP或域名 domain: 192.168.1.116 # 平台SIP服务IP或域名
port: 5060 port: 5060
transport: tcp # 也可以为udp transport: tcp # 也可以为udp
local-ip: 192.168.1.8 # 本服务的外网或可达IP
local-ip: 192.168.1.97 # 本服务的外网或可达IP
local-port: 5061 local-port: 5061
expires: 3600 expires: 3600

Loading…
Cancel
Save