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

基于Java实现CCTV视频流TCP采集并转推至Spring Boot端点的技术求助

问题描述

需要用Java实现一套方案:通过TCP Socket连接CCTV服务器,获取直播视频流后输出至Spring Boot端点(支持RTSP等协议),以便在Web播放器中展示,当前遇到技术瓶颈。

现有代码

try (Socket socket = new Socket(hostname, port)) {
            
            DataOutputStream outToServer = new DataOutputStream(socket.getOutputStream());
            BufferedReader inFromServer = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            // Login command
            outToServer.writeBytes(login);

            // Start LIVE video
            outToServer.writeBytes(live);

            ByteArrayOutputStream outStreamObj;
            ByteArrayOutputStream buffer = new ByteArrayOutputStream();
            byte[] byteChunk = new byte[1024];

            InputStream input = socket.getInputStream();
            int c = input.read(byteChunk);
            while (c != -1) {
                buffer.write(byteChunk, 0, c);
                c = input.read(byteChunk); // If placed in its own loop, it will read forever (or until the stream stops) so it will never exit
                
                BufferedImage image = ImageIO.read(new ByteArrayInputStream(buffer.toByteArray()));

                // create the object of ByteArrayOutputStream class
                outStreamObj = new ByteArrayOutputStream();

                // write the image into the object of ByteArrayOutputStream class
                ImageIO.write(image, "jpg", outStreamObj);

                // create the byte array from image
                byte[] byteArray = outStreamObj.toByteArray();

                // create the object of ByteArrayInputStream class
                // and initialized it with the byte array.
                ByteArrayInputStream inStreambj = new ByteArrayInputStream(byteArray);

                // read image from byte array
                BufferedImage newImage = ImageIO.read(inStreambj);

                // write output image
                ImageIO.write(newImage, "jpg", new File("outputImage.jpg"));
                System.out.println("Image generated from the byte array.");

            }
        } catch (UnknownHostException ex) {
            ...

        } catch (IOException ex) {
            ...
        }

当前问题

  • 执行到BufferedImage image = ImageIO.read(new ByteArrayInputStream(buffer.toByteArray()));时,image为null
  • 不确定当前实现思路是否正确
  • 仅临时保存图像到磁盘测试,实际不需要持久化

预期完整流程

  • 用户访问Angular网页
  • Web播放器加载指向Spring Boot后端的直播流URL
  • Spring Boot后端通过TCP连接CCTV服务器,并发送live命令
  • CCTV服务器在同一Socket会话中推送直播流字节
  • Spring Boot应用将这些字节转发至浏览器

解决方案建议

1. 纠正核心实现思路

你的代码错误地将连续的视频流字节直接当作单张JPG图片解析,但CCTV通过TCP推送的直播流通常是封装后的视频帧(比如H.264/H.265裸流,或带有头部标识的帧序列),并非完整的JPG文件。ImageIO.read返回null的原因就是输入的字节数组不是完整可识别的图片格式。

正确思路:不需要将字节转成BufferedImage,而是直接处理原始视频流字节,通过Spring Boot转发给前端,同时需要做协议转换(因为Web播放器通常不支持直接播放原始TCP流,需要转成HTTP-FLV、HLS或WebSocket流)。

2. 优化TCP流读取逻辑

现有代码的buffer会一直累积字节,导致内存溢出,且每次循环都尝试解析整个buffer是错误的。应该按视频帧的边界分割字节:

  • 先查阅CCTV服务器的协议文档,获取帧的头部标识(比如特定的字节序列)
  • 读取字节时,先识别帧头,再读取完整的一帧数据,避免累积无效字节
  • 示例调整后的读取逻辑:
try (Socket socket = new Socket(hostname, port)) {
    DataOutputStream outToServer = new DataOutputStream(socket.getOutputStream());
    InputStream input = socket.getInputStream();

    // 发送登录和直播命令
    outToServer.writeBytes(login);
    outToServer.writeBytes(live);
    outToServer.flush();

    byte[] frameHeader = new byte[4]; // 假设帧头是4字节,需根据实际协议调整
    byte[] frameBuffer = new byte[1024 * 1024]; // 1MB帧缓冲区,按需调整大小

    while (true) {
        // 读取帧头
        int headerRead = input.read(frameHeader);
        if (headerRead != frameHeader.length) break;

        // 根据帧头解析帧长度(需根据CCTV协议实现)
        int frameLength = parseFrameLength(frameHeader);

        // 读取完整帧数据
        int totalRead = 0;
        while (totalRead < frameLength) {
            int read = input.read(frameBuffer, totalRead, frameLength - totalRead);
            if (read == -1) break;
            totalRead += read;
        }

        // 处理完整帧:转发给前端(后续步骤实现)
        forwardFrameToClient(frameBuffer, 0, frameLength);
    }
} catch (IOException e) {
    e.printStackTrace();
}

// 需根据CCTV协议实现帧长度解析方法
private int parseFrameLength(byte[] header) {
    // 示例:假设前4字节是大端序的帧长度
    return ByteBuffer.wrap(header).getInt();
}

3. Spring Boot端点适配

Web播放器无法直接播放原始TCP流,需要将视频流封装成Web兼容的格式,推荐两种方案:

方案A:HTTP-FLV流端点

Spring Boot可以提供一个StreamingResponseBody的接口,将视频帧封装成FLV格式输出:

@GetMapping("/live/flv")
public ResponseEntity<StreamingResponseBody> liveFlv() {
    StreamingResponseBody responseBody = outputStream -> {
        // 这里复用上面的TCP连接和帧读取逻辑
        try (Socket socket = new Socket(hostname, port)) {
            DataOutputStream outToServer = new DataOutputStream(socket.getOutputStream());
            InputStream input = socket.getInputStream();

            outToServer.writeBytes(login);
            outToServer.writeBytes(live);
            outToServer.flush();

            // 先写入FLV文件头(固定格式)
            writeFlvHeader(outputStream);

            byte[] frameHeader = new byte[4];
            byte[] frameBuffer = new byte[1024 * 1024];

            while (!Thread.currentThread().isInterrupted()) {
                int headerRead = input.read(frameHeader);
                if (headerRead != frameHeader.length) break;

                int frameLength = parseFrameLength(frameHeader);
                int totalRead = 0;
                while (totalRead < frameLength) {
                    int read = input.read(frameBuffer, totalRead, frameLength - totalRead);
                    if (read == -1) break;
                    totalRead += read;
                }

                // 将视频帧封装成FLV Tag并写入输出流
                writeFlvTag(outputStream, frameBuffer, 0, frameLength);
                outputStream.flush();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    };

    return ResponseEntity.ok()
            .contentType(MediaType.parseMediaType("video/x-flv"))
            .body(responseBody);
}

// 实现FLV头和Tag的写入方法(需参考FLV格式规范)
private void writeFlvHeader(OutputStream out) throws IOException {
    // 写入FLV签名、版本、标志位、数据偏移等固定内容
    out.write(new byte[]{0x46, 0x4C, 0x56, 0x01, 0x05, 0x00, 0x00, 0x00, 0x09});
}

private void writeFlvTag(OutputStream out, byte[] frameData, int offset, int length) throws IOException {
    // 根据帧类型(视频/音频)封装成FLV Tag,需结合CCTV流的编码格式处理
    // 示例:假设是H.264视频帧
    byte[] tagHeader = new byte[11];
    tagHeader[0] = 0x09; // 视频Tag类型
    // 填充数据长度、时间戳等字段,具体参考FLV规范
    out.write(tagHeader);
    out.write(frameData, offset, length);
    // 写入Tag大小的4字节校验
    out.write(ByteBuffer.allocate(4).putInt(11 + length).array());
}

方案B:WebSocket实时推送

如果需要低延迟,可以用WebSocket推送帧数据,前端通过Canvas渲染:

@ServerEndpoint("/live/ws")
public class LiveWebSocket {
    @OnOpen
    public void onOpen(Session session) {
        // 启动TCP连接线程,推送帧数据到WebSocket
        new Thread(() -> {
            try (Socket socket = new Socket(hostname, port)) {
                DataOutputStream outToServer = new DataOutputStream(socket.getOutputStream());
                InputStream input = socket.getInputStream();

                outToServer.writeBytes(login);
                outToServer.writeBytes(live);
                outToServer.flush();

                byte[] frameHeader = new byte[4];
                byte[] frameBuffer = new byte[1024 * 1024];

                while (session.isOpen()) {
                    int headerRead = input.read(frameHeader);
                    if (headerRead != frameHeader.length) break;

                    int frameLength = parseFrameLength(frameHeader);
                    int totalRead = 0;
                    while (totalRead < frameLength) {
                        int read = input.read(frameBuffer, totalRead, frameLength - totalRead);
                        if (read == -1) break;
                        totalRead += read;
                    }

                    // 将帧数据转成Base64发送给前端(或直接发送二进制)
                    String base64Frame = Base64.getEncoder().encodeToString(Arrays.copyOfRange(frameBuffer, 0, frameLength));
                    session.getBasicRemote().sendText(base64Frame);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }).start();
    }
}

4. Web播放器兼容

  • 若用HTTP-FLV流:可以用flv.js播放器,在Angular中引入后直接播放:
import flvjs from 'flv.js';

// 初始化播放器
const videoElement = document.getElementById('live-video');
if (flvjs.isSupported()) {
    const flvPlayer = flvjs.createPlayer({
        type: 'flv',
        url: 'http://your-spring-boot-host/live/flv'
    });
    flvPlayer.attachMediaElement(videoElement);
    flvPlayer.load();
    flvPlayer.play();
}
  • 若用WebSocket:前端接收Base64后转成Blob,用Canvas渲染每一帧,或转成MediaSource流播放。

关键注意事项

  • 必须获取CCTV服务器的TCP协议文档:了解帧的结构、头部标识、编码格式(H.264/H.265等),否则无法正确解析流
  • 避免内存泄漏:TCP连接和流资源要确保在请求结束或异常时关闭
  • 性能优化:帧读取和转发要避免阻塞主线程,建议用异步线程处理
  • 异常处理:添加重连逻辑,避免CCTV连接断开后服务不可用

内容的提问来源于stack exchange,提问作者Comforse

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 16:05:23