| Author | SHA1 | Message | Date |
|---|---|---|---|
|
|
446f86682a | Merge branch 'master' of http://git.ht-atia.cn/qinyanlei/inspect-tcpserver | 3 weeks ago |
|
|
640c732b2b | /*SIP协议对接音频协议推流相关的尝试和修改*/ | 3 weeks ago |
| @ -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; | |||
| } | |||
| } | |||
| } | |||
| @ -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; | |||
| } | |||
| /** | |||
| * AVCC(length-prefixed)→ Annex-B(start 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; | |||
| } | |||
| } | |||
| @ -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); | |||
| } | |||
| } | |||
| @ -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(); | |||
| } | |||
| } | |||
| @ -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))); | |||
| } | |||
| } | |||
| @ -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 | |||
| }; | |||
| } | |||
| } | |||
| @ -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(); | |||
| } | |||
| } | |||
| @ -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 | |||
| }; | |||
| } | |||
| } | |||
| @ -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)); | |||
| } | |||
| } | |||
| @ -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 | |||
| }; | |||
| } | |||
| } | |||
| @ -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 | |||
| }; | |||
| } | |||
| } | |||
| @ -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; | |||
| } | |||
| } | |||
| } | |||
| @ -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(); | |||
| } | |||
| } | |||
| @ -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) { | |||
| } | |||
| } | |||
| } | |||
| @ -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 SR(B.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(); | |||
| } | |||
| }); | |||
| } | |||
| } | |||
| @ -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 + | |||
| '}'; | |||
| } | |||
| } | |||
| @ -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; | |||
| } | |||
| } | |||
| @ -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(); | |||
| } | |||
| } | |||
| @ -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(); | |||
| } | |||
| } | |||
| @ -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; | |||
| } | |||
| } | |||
| @ -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; | |||
| } | |||
| } | |||
| @ -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(); | |||
| } | |||
| @ -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; | |||
| } | |||
| } | |||
| @ -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 场景可忽略 | |||
| } | |||
| } | |||
| @ -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; | |||
| } | |||
| } | |||
| @ -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); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @ -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-B(00 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; | |||
| } | |||
| } | |||
| @ -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(); | |||
| } | |||
| } | |||
| @ -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; | |||
| } | |||
| } | |||
| @ -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); | |||
| } | |||
| } | |||
| @ -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(); | |||
| } | |||
| } | |||
| @ -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(); | |||
| } | |||
| } | |||
| @ -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) { | |||
| } | |||
| } | |||
| } | |||
| @ -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() {} | |||
| } | |||
| @ -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(); | |||
| } | |||
| } | |||
| @ -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(); | |||
| } | |||
| } | |||
| @ -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))); | |||
| } | |||
| } | |||
| @ -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(); | |||
| } | |||
| } | |||
| @ -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(); | |||
| } | |||
| } | |||