海康NVR录像回放SDK原始流转FLV视频流:基于Java的流媒体转码(无需安装第三方插件ffmpeg)
wlinker-video-monitor
代码地址:https://gitee.com/wlinker/wlinker-video-monitor
背景与需求
在安防监控、智能楼宇等场景中,海康威视设备作为行业主流硬件,常需要将录像回放功能集成到Web系统中。然而,海康设备的原始视频流格式(如私有协议或MPEG)通常无法直接在浏览器中播放。本文通过一个真实案例,介绍如何利用 JavaCV、海康SDK 和 Spring Boot,实现设备录像回放流的实时转码与FLV格式输出,最终在浏览器中无缝播放。
技术架构概览
核心组件
-
海康SDK(HCNetSDK):设备控制、录像回放流获取。
-
JavaCV(FFmpeg):视频流抓取、转码(MPEG→FLV)。
-
Spring Boot:HTTP接口封装、异步任务处理。
-
管道流(PipedStream):跨线程数据传输。
流程示意图
[海康设备]│↓ (SDK回调流数据) [HCNetTools] → PipedOutputStream│↓ (跨线程传输) [PipedInputStream] → [FFmpegGrabber]│↓ (转码) [FFmpegRecorder] → HTTP响应流(FLV)│↓ [浏览器/VLC播放器]
关键技术实现
1. 海康SDK流获取与管道传输(HCNetTools)
海康SDK通过回调函数返回原始视频流数据,需将其写入管道流供后续处理。核心代码如下:
public class HCNetTools {/*** 按时间回放录像* @param lChannel 通道号*/public void playBackByTime(LocalDateTime startTime, LocalDateTime endTime, int lChannel,Thread thread,PipedInputStream inputStream){playBackCallBack = null;try {pipeOutput = new PipedOutputStream(inputStream);} catch (Exception e) {StaticLog.error(e);}HCNetSDK.NET_DVR_VOD_PARA net_dvr_vod_para = new HCNetSDK.NET_DVR_VOD_PARA();net_dvr_vod_para.dwSize = net_dvr_vod_para.size();net_dvr_vod_para.struIDInfo.dwChannel = lChannel; //通道号//开始时间net_dvr_vod_para.struBeginTime = getHkTime(startTime);//停止时间net_dvr_vod_para.struEndTime = getHkTime(endTime);net_dvr_vod_para.hWnd = null; // 回放的窗口句柄,若置为空,SDK仍能收到码流数据,但不解码显示net_dvr_vod_para.write();iPlayBack = hCNetSDK.NET_DVR_PlayBackByTime_V40(lUserID, net_dvr_vod_para);if (iPlayBack <= -1) {System.out.println("按时间回放失败,错误码为" + hCNetSDK.NET_DVR_GetLastError());palybackFlay = true;return;}//开启取流IntByReference intP = new IntByReference(54962 * 1024);IntByReference intInlen1 = new IntByReference(0);boolean bCrtl = hCNetSDK.NET_DVR_PlayBackControl_V40(iPlayBack, HCNetSDK.NET_DVR_PLAYSTART, Pointer.NULL, 0, Pointer.NULL, intInlen1);if (bCrtl == false) {System.out.println("NET_DVR_PLAYSTART失败,错误码为" + hCNetSDK.NET_DVR_GetLastError());return;}playBackCallBack = new MyPlayDataCallBack(thread,pipeOutput);boolean bRet = hCNetSDK.NET_DVR_SetPlayDataCallBack(iPlayBack, playBackCallBack, Pointer.NULL);return;}class MyPlayDataCallBack implements HCNetSDK.FPlayDataCallBack{private final Thread thread;private boolean flag;private final PipedOutputStream pipeOutput;private AtomicInteger atomicInteger = new AtomicInteger(0);public MyPlayDataCallBack(Thread thread,PipedOutputStream pipeOutput){this.thread = thread;this.flag = false;this.pipeOutput = pipeOutput;}public void invoke(int lPlayHandle, int dwDataType, Pointer pBuffer, int dwBufSize, int dwUser) {//将设备发送过来的回放码流数据写入文件//StaticLog.info("回放码流回调次数:{}",atomicInteger.addAndGet(1));long offset = 0;ByteBuffer buffers = pBuffer.getByteBuffer(offset, dwBufSize);byte[] bytes = new byte[dwBufSize];buffers.rewind();buffers.get(bytes);try {this.pipeOutput.write(bytes);if(atomicInteger.addAndGet(1) % 50 == 0){int i = atomicInteger.get() / 50;//StaticLog.info("{},刷出通道:{}",atomicInteger.get(), i);this.pipeOutput.flush();if(i == 1){LockSupport.unpark(thread);}}} catch (IOException e) {stopPlayback();getPlaybacktimer().cancel();StaticLog.error(e,e.getLocalizedMessage());}}}}
关键设计:
-
多线程协作:通过
LockSupport
实现主线程阻塞与回调线程唤醒,确保FFmpeg在数据到达后开始处理。 -
缓冲控制:每50次写入刷新管道,平衡实时性与I/O开销。
2. FFmpeg实时转码与HTTP流输出(NetPlayBackController)
控制器从管道流读取数据并转码为FLV格式,通过HTTP响应流式输出:
@Slf4j
@Api(tags = "海康网络设备-录像回放原始流转flv流输出")
@RestController
@RequestMapping("/api/hikNet")
public class NetPlayBackController {@ResourceHikCameraAspect aspect;@GetMapping(value = "/playBackVideoOne")@ApiOperation(value = "海康网络设备-录像回放(基于原始流)-通道不复用", notes = "响应通道不复用,但较稳定")@Asyncpublic void playBackVideo2(@RequestParam String id, @RequestParam String startTime, @RequestParam String endTime, HttpServletResponse httpServletResponse) throws InterruptedException, IOException {//设置响应头httpServletResponse.setContentType("video/x-flv");httpServletResponse.setHeader("Connection", "keep-alive");httpServletResponse.setStatus(HttpServletResponse.SC_OK);httpServletResponse.flushBuffer();Thread mainThread = Thread.currentThread();MonitorDeviceDTO monitorDeviceDTO = VideoKeyUtils.getMonitorDeviceDtoById(id);String deviceCode = monitorDeviceDTO.getDeviceCode();String accessPlatform = monitorDeviceDTO.getAccessPlatform();if (!HikVisionAccessPlatformEnum.net.getAccessPlatform().equals(accessPlatform)) {throw new RuntimeException("不支持该平台");}LocalDateTime startLocalTime = DateUtil.parseLocalDateTime(startTime);LocalDateTime endLocalTime = DateUtil.parseLocalDateTime(endTime);//单次录像下载时长范围不能超过一小时if (startLocalTime.isAfter(endLocalTime)) {throw new RuntimeException("开始时间不能大于结束时间");}if (startLocalTime.plusHours(1).isBefore(endLocalTime)) {throw new RuntimeException("时间区间不能超过一小时");}Integer channelNo = monitorDeviceDTO.getChannelNo();aspect.logout(deviceCode);aspect.login(deviceCode);FFmpegFrameGrabber grabber = null;FFmpegFrameRecorder recorder = null;HCNetTools hcNetTools = NetDeviceCacheUtils.getHcNetTools(deviceCode);try (PipedInputStream inputStream = new PipedInputStream(1024 * 1024);) {Thread thread = Thread.currentThread();// 启动下载线程:将数据写入管道输出流hcNetTools.playBackByTime(startLocalTime, endLocalTime, channelNo, thread, inputStream);// 最多等待5秒(配置响应超时时间)long delayNanos = TimeUnit.SECONDS.toNanos(5);LockSupport.parkNanos(delayNanos);// 配置日志级别avutil.av_log_set_level(avutil.AV_LOG_FATAL);// 配置FFmpegFrameGrabber从管道输入流读取grabber = new FFmpegFrameGrabber(inputStream);// 明确指定输入格式(部分流需要)grabber.setFormat("mpeg");grabber.setOption("stimeout", "500000");//设置缓存大小,提高画质、减少卡顿花屏grabber.setOption("buffer_size", "1024000");grabber.start();// 配置FFmpegFrameRecorder直接输出FLV到HTTP响应流//创建转码器recorder = new FFmpegFrameRecorder(httpServletResponse.getOutputStream(), grabber.getImageWidth(),grabber.getImageHeight(),grabber.getAudioChannels());//配置转码器recorder.setFrameRate(grabber.getFrameRate());recorder.setSampleRate(grabber.getSampleRate());if (grabber.getAudioChannels() > 0) {recorder.setAudioChannels(grabber.getAudioChannels());recorder.setAudioBitrate(grabber.getAudioBitrate());recorder.setAudioCodec(avcodec.AV_CODEC_ID_AAC);//设置视频比例//recorder.setAspectRatio(grabber.getAspectRatio());}recorder.setFormat("flv");recorder.setVideoBitrate(grabber.getVideoBitrate());recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264);recorder.setVideoOption("color_range", "tv");recorder.start();Frame frame;while ((frame = grabber.grab()) != null) {recorder.record(frame);}} catch (Exception e) {log.error("播放视频时发生异常:{}", e.getMessage());} finally {// 释放资源if (recorder != null) {try {recorder.close();} catch (Exception e) {log.error("关闭recorder发生异常:{}", e.getMessage());}}if (grabber != null) {try {grabber.close();} catch (Exception e) {log.error("关闭grabber发生异常:{}", e.getMessage());}}hcNetTools.stopPlayback();hcNetTools.deviceLogout();}}}
关键优化:
-
低延迟:直接传递帧对象,避免内存复制。
-
自适应参数:从原始流继承分辨率、帧率等属性,确保兼容性。
总结与展望
本文方案通过 海康SDK + JavaCV + 管道流 的组合,实现了设备录像回放流的实时转码与Web输出。其优势在于:
-
高实时性:从设备到浏览器的端到端延迟可控。
-
轻量级:无需中间服务器转存视频文件。
相关文章:

海康NVR录像回放SDK原始流转FLV视频流:基于Java的流媒体转码(无需安装第三方插件ffmpeg)
wlinker-video-monitor 代码地址:https://gitee.com/wlinker/wlinker-video-monitor 背景与需求 在安防监控、智能楼宇等场景中,海康威视设备作为行业主流硬件,常需要将录像回放功能集成到Web系统中。然而,海康设备的原始视频流…...
深入理解设计模式:工厂模式、单例模式
深入理解设计模式:工厂模式、单例模式 设计模式是软件开发中解决常见问题的可复用方案。本文将详细介绍两种种重要的创建型设计模式:工厂模式、单例模式,并提供Java实现示例。 一、工厂模式 工厂模式是一种创建对象的设计模式,…...

运维Linux之Ansible详解学习(更新中)
什么是Ansible Ansible 是一款新出现的自动化运维工具,基于 Python 开发。以下是对它的详细介绍: 功能特点:集合了众多运维工具的优点,能实现批量系统配置、批量程序部署、批量运行命令等功能。它是基于模块工作的,本…...

深入浅出IIC协议 - 从总线原理到FPGA实战开发 -- 第三篇:Verilog实现I2C Master核
第三篇:Verilog实现I2C Master核 副标题 :从零构建工业级I2C控制器——代码逐行解析与仿真实战 1. 架构设计 1.1 模块分层设计 三层架构 : 层级功能描述关键信号PHY层物理信号驱动与采样sda_oe, scl_oe控制层协议状态机与数据流控制state…...
网络世界的“变色龙“:动态IP如何重构你的数据旅程?
在深秋的下午调试代码时,我偶然发现服务器日志中出现异常登录记录——IP地址显示为某个境外数据中心。更有趣的是,当我切换到公司VPN后,这个"可疑IP"竟自动消失在了防火墙监控列表中。这个瞬间让我意识到:现代网络架构中…...
进阶-自定义类型(结构体、位段、枚举、联合)
自定义类型:结构体,枚举,联合 结构体 结构体类型的声明 结构的自引用 结构体变量的定义和初始化 结构体内存对齐 结构体传参 结构体实现位段(位段的填充&可移植性) 枚举 枚举类型的定义 枚举的优点 枚举的使用 联合 联合类型的定义 联…...
5G 网络全场景注册方式深度解析:从信令交互到报文分析
摘要 本文全面梳理 5G 网络包含的初始注册、移动性注册更新、紧急注册、周期性注册更新、服务请求触发注册、切换触发注册、基于策略的注册更新等多种注册方式。详细阐述每种注册方式的触发条件、信令流程、关键报文结构,结合对比分析与实际案例,助力读者深入理解 5G 网络接…...

ARM笔记-嵌入式系统基础
第一章 嵌入式系统基础 1.1嵌入式系统简介 1.1.1嵌入式系统定义 嵌入式系统定义: 嵌入式系统是以应用为中心,以计算机技术为基础,软硬件可剪裁,对功能、可靠性、成本、体积、功耗等有严格要求的专用计算机系统 ------Any devic…...
一文讲透golang channel 的特点、原理及使用场景
在 Go 语言中,通道(Channel) 是实现并发编程的核心机制之一,基于 CSP(Communicating Sequential Processes) 模型设计。它不仅用于协程(Goroutine)之间的数据传递,还通过…...

upload-labs通关笔记-第19关文件上传之条件竞争
系列目录 upload-labs通关笔记-第1关 文件上传之前端绕过(3种渗透方法) upload-labs通关笔记-第2关 文件上传之MIME绕过-CSDN博客 upload-labs通关笔记-第3关 文件上传之黑名单绕过-CSDN博客 upload-labs通关笔记-第4关 文件上传之.htacess绕过-CSDN…...

第5章:任务间通信机制(IPC)全解析
💬 在多线程开发中,线程之间如何协作?如何让一个线程产生数据,另一个线程消费数据?本章聚焦 Zephyr 提供的多种任务间通信机制(IPC)及实战使用技巧。 📚 本章导读 你将学到: Zephyr 提供的常用 IPC 接口:FIFO、消息队列、邮箱、信号量 每种机制适用场景和用法对比…...

CAPL自动化-诊断Demo工程
文章目录 前言一、诊断控制面板二、诊断定义三、发送诊断通过类.方法的方式req.SetParameterdiagSetParameter四、SendRequestAndWaitForResponse前言 本文将介绍CANoe的诊断自动化测试,工程可以从CANoe的 Sample Configruration 界面打开,也可以参考下面的路径中打开(以实…...

SVN被锁定解决svn is already locked
今天遇到一个问题,svn 在提交代码的时候出现了svn is already locked,解决方案...

【深度学习】1. 感知器,MLP, 梯度下降,激活函数,反向传播,链式法则
一、感知机 对于分类问题,我们设定一个映射,将x通过函数f(x)映射到y 1. 感知机的基本结构 感知机(Perceptron)是最早期的神经网络模型,由 Rosenblatt 在 1958 年提出,是现代神经网络和深度学习模型的雏形…...

云原生安全:网络协议TCP详解
🔥「炎码工坊」技术弹药已装填! 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 (注:文末附可视化流程图与专有名词说明表) 1. 基础概念 TCP(Transmission Control Protocol)是…...

使用CentOS部署本地DeekSeek
一、查看服务器的操作系统版本 cat /etc/centos-release二、下载并安装ollama 1、ollama下载地址: Releases ollama/ollama GitHubGet up and running with Llama 3.3, DeepSeek-R1, Phi-4, Gemma 3, Mistral Small 3.1 and other large language models. - Re…...
Spring Boot与Eventuate Tram整合:构建可靠的事件驱动型分布式事务
精心整理了最新的面试资料和简历模板,有需要的可以自行获取 点击前往百度网盘获取 点击前往夸克网盘获取 一、引言 在现代微服务架构中,分布式事务管理一直是复杂系统中的核心挑战之一。传统的两阶段提交(2PC)方案存在性能瓶颈&…...
Python:从脚本语言到工业级应用的传奇进化
一、Python的诞生:一场喜剧与编程的奇妙相遇 1989年的冬天,荷兰程序员Guido van Rossum在阿姆斯特丹的CWI研究所里,用一段独特的代码开启了编程语言的新纪元。这个被命名为"Python"的项目,灵感并非源自冷血的蟒蛇,而是源于Guido对英国喜剧团体Monty Python的痴…...
【排序算法】典型排序算法 Java实现
以下是典型的排序算法分类及对应的 Java 实现,包含时间复杂度、稳定性说明和核心代码示例: 一、比较类排序(通过元素比较) 1. 交换排序 ① 冒泡排序 时间复杂度:O(n)(优化后最优O(n)) 稳定性&…...
node.js如何实现双 Token + Cookie 存储 + 无感刷新机制
node.js如何实现双 Token Cookie 存储 无感刷新机制 为什么要实施双token机制? 优点描述安全性Access Token 短期有效,降低泄露风险;Refresh Token 权限受限,仅用于获取新 Token用户体验用户无需频繁重新登录,Toke…...
[DS]使用 Python 库中自带的数据集来实现上述 50 个数据分析和数据可视化程序的示例代码
使用 Python 库中自带的数据集来实现上述 50 个数据分析和数据可视化程序的示例代码 摘要:由于 sample_data.csv 是一个占位符文件,用于代表任意数据集,我将使用 Python 库中自带的数据集来实现上述 50 个数据分析和数据可视化程序的示例代码…...
探索智能仓颉
探索智能仓颉:Cangjie Magic体验有感 一、引言 在人工智能和智能体开发领域,新的技术和框架不断涌现,推动着行业的快速发展。2025年3月,仓颉社区开源了Cangjie Magic,这是一个基于仓颉编程语言原生构建的LLM Agent开…...
Ubuntu 上开启 SSH 服务、禁用密码登录并仅允许密钥认证
1. 安装 OpenSSH 服务 如果尚未安装 SSH 服务,运行以下命令: sudo apt update sudo apt install openssh-server2. 启动 SSH 服务并设置开机自启 sudo systemctl start ssh sudo systemctl enable ssh3. 生成 SSH 密钥对(本地机器…...

LLMs之Qwen:《Qwen3 Technical Report》翻译与解读
LLMs之Qwen:《Qwen3 Technical Report》翻译与解读 导读:Qwen3是Qwen系列最新的大型语言模型,它通过集成思考和非思考模式、引入思考调度机制、扩展多语言支持以及采用强到弱的知识等创新技术,在性能、效率和多语言能力方面都取得…...
springboot3 configuration
1 多数据库配置 github: https://github.com/baomidou/dynamic-datasource 使用DS()注解来切换数据库 详情介绍:https://www.kancloud.cn/tracy5546/dynamic-datasource/2264611 注意:DS 可以注解在方法上或类上,同时存在就近原则 方法上注…...

从工程实践角度分析H.264与H.265的技术差异
作为音视频从业者,我们时刻关注着视频编解码技术的最新发展。RTMP推流、轻量级RTSP服务、RTMP播放、RTSP播放等模块是大牛直播SDK的核心功能,在这些模块的实现过程中,H.264和H.265两种视频编码格式的应用实践差异是我们技术团队不断深入思考的…...

如何设计一个高性能的短链设计
1.什么是短链 短链接(Short URL) 是通过算法将长 URL 压缩成简短字符串的技术方案。例如将 https://flowus.cn/veal/share/3306b991-e1e3-4c92-9105-95abf086ae4e 缩短为 https://sourl.cn/aY95qu,用户点击短链时会自动重定向到原始长链接。其…...

提升工作效率的可视化笔记应用程序
StickyNotes桌面便签软件介绍 StickyNotes是一款极为简洁的桌面便签应用程序,让您能够快速记录想法、待办事项或其他重要信息。这款工具操作极其直观,只需输入文字内容,选择合适的字体大小和颜色,然后点击添加按钮即可创建个性化…...

11|省下钱买显卡,如何利用开源模型节约成本?
不知道课程上到这里,你账户里免费的5美元的额度还剩下多少了?如果你尝试着完成我给的几个数据集里的思考题,相信这个额度应该是不太够用的。而ChatCompletion的接口,又需要传入大量的上下文信息,实际消耗的Token数量其…...
GDB调试工具详解
GDB调试工具详解 一、基本概念 调试信息 编译时需添加 -g 选项(如 gcc -g -o program program.c),生成包含变量名、函数名、行号等调试信息的可执行文件。断点(Breakpoint) 程序执行到指定位置(函数、行号…...