javaCV简单解析gb28181的rtp ps流,并推流到rtmp服务

使用 JavaCV 简单解析 GB28181 RTP PS 流并推流到 RTMP

GB28181 协议中,媒体流通常以 PS (Program Stream) 格式封装在 RTP 包中传输(RTP over UDP 或 TCP)。JavaCV(基于 FFmpeg)可以直接处理 PS 流,但对于 RTP 封装的 PS 流,需要先剥离 RTP 头,组装成完整的 PS 数据,然后喂给 FFmpegFrameGrabber 解码,再用 FFmpegFrameRecorder 转推 RTMP。

核心思路(简单实现)

  1. 接收 RTP 包:使用 Netty 或 Socket 接收 RTP 数据(UDP/TCP)。
  2. 剥离 RTP 头:RTP 头固定 12 字节(无扩展时),PS 数据从第 13 字节开始(UDP)或第 15 字节(TCP,前面有 2 字节长度 + $ 标志)。
  3. 组包 & 解析 PS:由于 PS 帧可能被 RTP 分包,需要按序列号(seq)排序缓存,组装完整 PS 帧。识别 PS 头(0x000001BA)、PES 头,提取 H.264/ES 数据。
  4. 管道喂流:使用 PipedOutputStream + PipedInputStream 将组装的 PS 数据写入管道。
  5. JavaCV 拉流解码FFmpegFrameGrabber 从管道读取 PS 流,设置格式为 “mpegps” 或直接 “ps”。
  6. 推 RTMPFFmpegFrameRecorder 录制抓取的帧到 RTMP URL。

依赖(Maven)

<dependency>
    <groupId>org.bytedeco</groupId>
    <artifactId>javacv-platform</artifactId>
    <version>1.5.10</version> <!-- 最新版 -->
</dependency>

示例代码(基于 CSDN/博客常见实现,简化版)

import org.bytedeco.javacv.*;
import org.bytedeco.javacpp.avcodec;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.util.concurrent.ConcurrentLinkedDeque;

// RTP 接收处理器(示例,用 Netty 或 UDP Socket 替换)
public class RtpPsParser implements Runnable {
    private PipedOutputStream pos = new PipedOutputStream();
    private PipedInputStream pis = new PipedInputStream(pos, 1024 * 1024);
    private ConcurrentLinkedDeque<byte[]> packetQueue = new ConcurrentLinkedDeque<>();
    private boolean running = true;

    // 假设这里接收到 RTP 数据包(byte[] data)
    public void onRtpPacket(byte[] data, int offset, int len) {
        // 剥离 RTP 头(UDP 示例,TCP 多 +2 字节长度)
        byte[] psData = new byte[len - 12];
        System.arraycopy(data, 12, psData, 0, psData.length);
        packetQueue.offer(psData); // 简单缓存,实际需按 seq 排序去重
    }

    @Override
    public void run() {
        while (running) {
            if (!packetQueue.isEmpty()) {
                byte[] psChunk = packetQueue.poll();
                try {
                    pos.write(psChunk); // 写入管道,组装 PS 流
                    pos.flush();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public PipedInputStream getInputStream() {
        return pis;
    }
}

// 主推流类
public class Gb28181ToRtmp {
    public static void main(String[] args) throws Exception {
        RtpPsParser parser = new RtpPsParser();
        new Thread(parser).start();

        // Grabber:从管道读取 PS 流
        FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(parser.getInputStream());
        grabber.setFormat("ps"); // 或 "mpegps"
        grabber.setOption("rtsp_transport", "tcp"); // 如果是 TCP  interleaved
        grabber.setVideoCodec(avcodec.AV_CODEC_ID_H264);
        grabber.setFrameRate(25);
        grabber.setImageWidth(1920); // 根据实际调整
        grabber.setImageHeight(1080);
        grabber.start();

        // Recorder:推 RTMP
        FFmpegFrameRecorder recorder = new FFmpegFrameRecorder("rtmp://your-server/live/stream", grabber.getImageWidth(), grabber.getImageHeight());
        recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264);
        recorder.setFormat("flv");
        recorder.setFrameRate(grabber.getFrameRate());
        recorder.setVideoBitrate(2000000);
        recorder.setVideoOption("preset", "veryfast");
        recorder.setVideoOption("tune", "zerolatency");
        recorder.start();

        Frame frame;
        while ((frame = grabber.grabFrame()) != null) {
            recorder.record(frame); // 直接转发帧
        }

        grabber.stop();
        recorder.stop();
    }
}

注意事项

  • RTP 组包:实际需处理 seq、标记位(Marker 表示帧结束)、丢包重排序。参考博客中的 SsrcUdpHandlerSsrcTcpHandler 类。
  • TCP vs UDP:TCP 有 $ + 长度(2 字节),需额外剥离。
  • 性能:管道缓冲设大,避免阻塞。长时间运行需处理 I 帧检测、缓存清理。
  • 测试:先用 Wireshark 抓包确认 PS 头(0x000001BA),或用 FFmpeg 测试:ffplay rtp://ip:port(需 SDP)。
  • 替代简单方案:如果不手动解析 RTP,可用 ZLMediaKit/SRS(支持直接接收 GB28181 PS RTP 并转 RTMP/HLS),无需 JavaCV 编码。

此方式已验证可行(参考 eguid 等博客),适合学习/小型项目。大规模建议用成熟框架如 ZLMediaKit。需要完整 RTP 解析代码或特定厂商适配,欢迎提供更多细节!

文章已创建 3572

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

相关文章

开始在上面输入您的搜索词,然后按回车进行搜索。按ESC取消。

返回顶部