You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot中解码RTSP摄像头H264流至前端的技术求助

Spring Boot中RTSP摄像头H264流解码并推送到前端的解决方案

核心问题拆解

  • 从WebSocket接收RTSP摄像头输出的H264编码字节流(以MP4封装的MDAT盒形式传输)
  • 需要适配前端播放格式,替代失败的JavaCV/FFmpeg方案
  • 当前尝试通过Spring SSE发送提取的MDAT内容,但未达到预期效果

可行落地方案

1. 先确认H264流的封装与NALU边界

MDAT中的H264数据是连续的NALU单元,每个NALU以0x00000001或0x000001的起始码分隔。直接发送原始MDAT内容可能缺少边界标识,导致前端无法解析。

提取NALU的Java代码示例

// 从MDAT字节数组中提取带起始码的NALU列表
public List<byte[]> extractNalus(byte[] mdatData) {
    List<byte[]> naluList = new ArrayList<>();
    int offset = 0;
    int dataLen = mdatData.length;

    while (offset < dataLen) {
        // 查找起始码
        int startCodePos = findStartCode(mdatData, offset);
        if (startCodePos == -1) break;

        // 查找下一个起始码,确定当前NALU长度
        int nextStartCodePos = findStartCode(mdatData, startCodePos + 4);
        int naluLen = (nextStartCodePos == -1) ? dataLen - startCodePos : nextStartCodePos - startCodePos;

        byte[] nalu = Arrays.copyOfRange(mdatData, startCodePos, startCodePos + naluLen);
        naluList.add(nalu);
        offset = startCodePos + naluLen;
    }
    return naluList;
}

private int findStartCode(byte[] data, int startOffset) {
    // 查找4字节起始码
    for (int i = startOffset; i < data.length - 3; i++) {
        if (data[i] == 0 && data[i+1] == 0 && data[i+2] == 0 && data[i+3] == 1) {
            return i;
        }
    }
    // 查找3字节起始码
    for (int i = startOffset; i < data.length - 2; i++) {
        if (data[i] == 0 && data[i+1] == 0 && data[i+2] == 1) {
            return i;
        }
    }
    return -1;
}

2. 前端直接播放H264(无需后端解码)

后端无需解码H264,只需将提取的NALU通过WebSocket以二进制帧发送,前端使用MediaSource API配合video标签播放,这是最高效的方案:

Spring Boot WebSocket二进制发送代码

@Controller
public class CameraStreamController {
    @Autowired
    private SimpMessagingTemplate messagingTemplate;

    // 处理从WebSocket接收的摄像头流
    @MessageMapping("/camera/stream")
    public void handleCameraStream(byte[] payload) {
        // 假设payload为MDAT盒内容,提取NALU
        List<byte[]> nalus = extractNalus(payload);
        for (byte[] nalu : nalus) {
            // 以二进制帧发送到前端订阅主题
            messagingTemplate.convertAndSend("/topic/camera/feed", nalu);
        }
    }
}

前端播放JavaScript代码

const video = document.getElementById('camera-video');
const mediaSource = new MediaSource();
video.src = URL.createObjectURL(mediaSource);

let sourceBuffer;
mediaSource.addEventListener('sourceopen', () => {
    // 匹配摄像头H264 profile,示例为Baseline profile
    sourceBuffer = mediaSource.addSourceBuffer('video/mp4; codecs="avc1.42E01E"');
    const socket = new WebSocket('ws://localhost:8080/ws');
    socket.binaryType = 'arraybuffer';
    
    socket.onmessage = (event) => {
        if (!sourceBuffer.updating) {
            sourceBuffer.appendBuffer(event.data);
        }
    };
});

3. 若必须后端解码(如需处理帧数据)

如果需要对帧进行处理(比如人脸识别),改用FFmpeg的JNI绑定而非JavaCV全量包,减少依赖问题:

  • 引入FFmpeg Java绑定依赖:org.bytedeco:ffmpeg-platform
  • 初始化FFmpeg解码器,将H264字节流解码为RGB/YUV帧
  • 将帧编码为前端支持的格式(JPEG/WEBP)后发送

简化FFmpeg解码示例

import org.bytedeco.ffmpeg.global.avcodec;
import org.bytedeco.ffmpeg.global.avutil;
import org.bytedeco.ffmpeg.*;

public class H264Decoder {
    private AVCodecContext codecCtx;

    public H264Decoder() {
        avutil.av_log_set_level(avutil.AV_LOG_ERROR);
        AVCodec codec = avcodec.avcodec_find_decoder(avcodec.AV_CODEC_ID_H264);
        codecCtx = avcodec.avcodec_alloc_context3(codec);
        avcodec.avcodec_open2(codecCtx, codec, null);
    }

    public AVFrame decode(byte[] h264Data) {
        AVPacket pkt = new AVPacket();
        pkt.data(avutil.av_malloc(h264Data.length));
        pkt.size(h264Data.length);
        avutil.av_memcpy(pkt.data(), h264Data, h264Data.length);

        AVFrame frame = avutil.av_frame_alloc();
        int ret = avcodec.avcodec_send_packet(codecCtx, pkt);
        if (ret >= 0) {
            ret = avcodec.avcodec_receive_frame(codecCtx, frame);
            if (ret >= 0) {
                return frame;
            }
        }
        avutil.av_frame_free(frame);
        return null;
    }
}

调试验证步骤

  1. 用FFmpeg验证提取的MDAT内容是否为有效H264:
    ffmpeg -f h264 -i mdat_dump.bin -c copy test.mp4
    
    若能生成可播放的MP4,说明流本身有效,问题出在传输/解析逻辑。
  2. 检查前端MediaSource的codec参数是否与摄像头H264 profile匹配。

内容的提问来源于stack exchange,提问作者ashif-ismail

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.30 20:24:33