java获取近期视频流关键帧与截图
1、背景
最近在做视频转发的开发时,遇到一个问题,前端订阅播放h264视频流时,有时会出现一段时间黑屏,经过测试发现是没有收到关键帧,只有第一帧是关键帧才能保证后续播放正常。所以后端需要实现一个功能,就是前端在进入播放页面时,后端把最近的一个关键帧发过去。
2、思路(环形缓存区)
后端接收到的视频流是一个个的字节数组,所以在接收时没法直接判断一帧的开始和结束,需要将最近的一段视频流截取出来,然后利用ffmpeg工具进行整体的解析和关键帧提取。
查看ffmpeg工具的代码,可以发现ffmpeg工具入参是inputstream,该工具会不断调用inputstream的read方法进行字节的读取。所以就想通过一个环形缓存区不断的记录最新的一段视频流数据。该环形缓存区再实现inputstream的接口,重写read方法,read读取的开始位置即是环形缓存区头,到环形缓存区的尾时自动结束。
3、依赖包
<!-- javacv -->
<dependency><groupId>org.bytedeco</groupId><artifactId>javacpp</artifactId><version>1.4.3</version>
</dependency>
<dependency><groupId>org.bytedeco</groupId><artifactId>javacv</artifactId><version>1.4.3</version>
</dependency>
<dependency><groupId>org.bytedeco.javacpp-presets</groupId><artifactId>ffmpeg-platform</artifactId><version>4.0.2-1.4.3</version>
</dependency>
4、环形缓存区定义
public class CycleBufferInputStream extends InputStream {/************************************************ 环形缓冲区 ***********************************************/private ByteBuffer buffer = null;private int readPos = 0; //将要读取的位置private int writePos = 0; //将要写入的位置private boolean isCycle = false; //判断是否已经形成一个环public CycleBufferInputStream(int capacity) {this.buffer = ByteBuffer.allocateDirect(capacity);}/*** 将字节数组以覆盖的方式放入环形缓冲区*/public void put(byte[] bytes) {int used = buffer.capacity() - buffer.position();if (used < bytes.length) {buffer.put(bytes, 0, used);buffer.clear();buffer.put(bytes, used, bytes.length - used);isCycle = true;} else if (used == bytes.length) {buffer.put(bytes, 0, used);buffer.clear();isCycle = true;} else {buffer.put(bytes, 0, bytes.length);}writePos = buffer.position();}/*** 定位读取的初始位置(执行inputstream 读取前,必须要先调用该方法)*/public void readPrepare() {if (buffer.capacity() == writePos || !isCycle) {readPos = 0;} else {readPos = buffer.position() + 1;}}/*************************************************** 输入流传输 ***************************************************//*** Reads the next byte of data from the input stream. The value byte is returned as an int in the range 0 to 255. If no byte is available because the end of the stream has been reached, the value -1 is returned. This method blocks until input data is available, the end of the stream is detected, or an exception is thrown.* A subclass must provide an implementation of this method.* Returns: the next byte of data, or -1 if the end of the stream is reached.* Throws: IOException – if an I/O error occurs.*/@Overridepublic int read() throws IOException {if (readPos == buffer.capacity()) readPos = 0;if (readPos == writePos) return -1;int value = buffer.get(readPos++);if (value < 0) value = value + 256;return value;}}
5、从环形缓存区提取关键帧
/*** 从环形缓存环获取最近一帧关键帧字节数组* 这里返回的堆外内存,所以注意要及时进行内存释放* @param inputStream*/public static ByteBuffer dealVideo(InputStream inputStream) {try {int j = 0;FFmpegFrameGrabber ff = new FFmpegFrameGrabber(inputStream);ff.start();Frame frame = null;Frame last = null;while ((frame = ff.grabKeyFrame()) != null && frame.image != null) {last = frame.clone();System.out.println("获取一帧" + j++);}ff.stop();if (last != null) {System.out.println("获取最近的一个关键帧");ByteBuffer byteBuffer = (ByteBuffer)last.image[0];return byteBuffer;}} catch (Exception e) {log.error("提取最近一个关键帧异常\n",e);}return null;}
6、使用
public static void main(String[] args) throws Exception {CycleBufferInputStream stream = new CycleBufferInputStream(1024 * 1024 * 10);FileInputStream fis = new FileInputStream("D:\\tmp-data\\1694511149969.h264");byte[] bytes = new byte[fis.available()];fis.read(bytes);stream.put(bytes);stream.readPrepare();dealVideo(stream);}
7、扩展:获取近期视频的截图
其实就是从近期的关键帧中提取出图片,关键代码如下:
public static void dealImage(InputStream inputStream) {try {Java2DFrameConverter converter = new Java2DFrameConverter();FFmpegFrameGrabber ff = new FFmpegFrameGrabber(inputStream);ff.start();int j = 0;Frame frame = null;Frame last = null;while ((frame = ff.grabImage()) != null) {last = frame.clone();System.out.println("获取一张图片" + j++);}ff.stop();if (last != null && last.image != null) {System.out.println("存储最后一张图片 ");BufferedImage fecthedImage = converter.getBufferedImage(last);File screenshotFile = new File("D:\\tmp-data\\", System.currentTimeMillis() + ".jpg");ImageIO.write(fecthedImage, "jpg", screenshotFile);}} catch (Exception e) {log.error("提取最近一个图片异常\n",e);}}
8、整体代码
package com.qq.utils;import lombok.extern.slf4j.Slf4j;
import org.bytedeco.javacv.FFmpegFrameGrabber;
import org.bytedeco.javacv.Frame;
import org.bytedeco.javacv.Java2DFrameConverter;import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.*;
import java.nio.ByteBuffer;@Slf4j
public class CycleBufferInputStream extends InputStream {/************************************************ 环形缓冲区 ***********************************************/private ByteBuffer buffer = null;private int readPos = 0; //将要读取的位置private int writePos = 0; //将要写入的位置private boolean isCycle = false; //判断是否已经形成一个环public CycleBufferInputStream(int capacity) {this.buffer = ByteBuffer.allocateDirect(capacity);}/*** 将字节数组以覆盖的方式放入环形缓冲区*/public void put(byte[] bytes) {int used = buffer.capacity() - buffer.position();if (used < bytes.length) {buffer.put(bytes, 0, used);buffer.clear();buffer.put(bytes, used, bytes.length - used);isCycle = true;} else if (used == bytes.length) {buffer.put(bytes, 0, used);buffer.clear();isCycle = true;} else {buffer.put(bytes, 0, bytes.length);}writePos = buffer.position();}/*** 定位读取的初始位置(执行inputstream方法前,必须要先调用该方法)*/public void readPrepare() {if (buffer.capacity() == writePos || !isCycle) {readPos = 0;} else {readPos = buffer.position() + 1;}}/*************************************************** 输入流传输 ***************************************************/@Overridepublic int read() throws IOException {if (readPos == buffer.capacity()) readPos = 0;if (readPos == writePos) return -1;int value = buffer.get(readPos++);if (value < 0) value = value + 256;return value;}/*** 从环形缓存环获取最近一帧关键帧字节数组* 这里返回的堆外内存,所以注意要及时进行内存释放* @param inputStream*/public static ByteBuffer dealVideo(InputStream inputStream) {try {int j = 0;FFmpegFrameGrabber ff = new FFmpegFrameGrabber(inputStream);ff.start();Frame frame = null;Frame last = null;while ((frame = ff.grabKeyFrame()) != null && frame.image != null) {last = frame.clone();System.out.println("获取一帧" + j++);}ff.stop();if (last != null) {System.out.println("获取最近的一个关键帧");ByteBuffer byteBuffer = (ByteBuffer)last.image[0];return byteBuffer;}} catch (Exception e) {log.error("提取最近一个关键帧异常\n",e);}return null;}public static void dealImage(InputStream inputStream) {try {Java2DFrameConverter converter = new Java2DFrameConverter();FFmpegFrameGrabber ff = new FFmpegFrameGrabber(inputStream);ff.start();int j = 0;Frame frame = null;Frame last = null;while ((frame = ff.grabImage()) != null) {last = frame.clone();System.out.println("获取一张图片" + j++);}ff.stop();if (last != null && last.image != null) {System.out.println("存储最后一张图片 ");BufferedImage fecthedImage = converter.getBufferedImage(last);File screenshotFile = new File("D:\\tmp-data\\", System.currentTimeMillis() + ".jpg");ImageIO.write(fecthedImage, "jpg", screenshotFile);}} catch (Exception e) {log.error("提取最近一个图片异常\n",e);}}}
相关文章:
java获取近期视频流关键帧与截图
1、背景 最近在做视频转发的开发时,遇到一个问题,前端订阅播放h264视频流时,有时会出现一段时间黑屏,经过测试发现是没有收到关键帧,只有第一帧是关键帧才能保证后续播放正常。所以后端需要实现一个功能,就…...
arcgis 批量删除Table中的某些Field
当shp或者table文件较少时,可以手动删除每个文件中的某些字段,当文件较多时,就需要使用arcpy或者model进行处理。...
工厂设备扫码使用售卖联网开发需要怎么开发开源代码?
我们将详细介绍如何使用开源代码开发一套用于工厂设备联网统计的系统。我们将详细讨论所需硬件组件的选择、开源框架和库的使用、软件开发流程以及最后的集成和部署。在这个过程中,我们将提供实用的操作步骤和指导,帮助你更容易地完成这个复杂的任务。 …...
软考高级之132个工具和技术
分类 工具与技术 描述 数据收集 头脑风暴 在短时间内获得大量创意,适用于团队环境,需要引导者引导(过程中可以天马行空,不要打断) 包括:头脑风暴、头脑写作 头脑写作:在开始小组创意讨论之…...
算法通过村第十八关-回溯|白银笔记|经典问题
文章目录 前言组合总和问题分割回文串子集问题排序问题字母大小写全排列单词搜索总结 前言 提示:我不愿再给你写信了。因为我终于感到,我们的全部通信知识一个大大的幻影,我们每个人知识再给自己写信。 --安德烈纪德 回溯主要解决一些暴力枚举…...
vue2 集成 - 超图 - SuperMap iClient3D for WebGL 及常用方法
文章目录 1:下载SuperMap iClient3D for WebGL2:格式化项目中所用的依赖包3:vue2 项目引入4:vue2 页面使用常见方法4.1 创建三维场景,引入在线地图资源,定位到指定位置4.2 坐标拾取4.3 用户输入事件4.4 拾取实体4.5 实体改变监听事件4.6 双击全屏4.7 相机移动事件4.8 添加…...
应用程序服务器/事件驱动编程/CommonJS介绍
目录 应用程序服务器事件驱动编程CommonJS 👍 点赞,你的认可是我创作的动力! ⭐️ 收藏,你的青睐是我努力的方向! ✏️ 评论,你的意见是我进步的财富! 应用程序服务器 应用程序服务器是一种用…...
第二十九章 目标检测中的测试模型评价指标(车道线感知)
前言 近期参与到了手写AI的车道线检测的学习中去,以此系列笔记记录学习与思考的全过程。车道线检测系列会持续更新,力求完整精炼,引人启示。所需前期知识,可以结合手写AI进行系统的学习。 介绍 自动驾驶的一大前提是保证人的安全…...
OceanBase 如何通过日志观测冻结转储流程?
本文旨在通过日志解析 OceanBase 的冻结转储流程,以其冻结检查线程为切入点,以租户(1002)的线程名为例。 作者:陈慧明,爱可生测试工程师,主要参与 DMP 和 DBLE 自动化测试项目。 爱可生开源社区…...
深度图(Depth Map)
文章目录 深度图深度图是什么深度图的获取方式激光雷达或结构光等传感器的方法激光雷达RGB-D相机 双目或多目相机的视差信息计算深度采用深度学习模型估计深度 深度图的应用场景扩展阅读 深度图 深度图是什么 深度图(depth map)是一种灰度图像…...
Ubuntu下Anaconda安装
Ubuntu下Anaconda安装 进入anaconda官网 https://www.anaconda.com/ 下载Linux64位版本; 将下载好的".sh"文件放入虚拟机中; 运行指令sudo bash Anaconda3-2023.09-0-Linux-x86_64.sh 此后会自动加载安装程序,中途会停止两次&am…...
目标检测回归损失函数(看情况补...)
文章目录 L1 loss-平均绝对误差(Mean Absolute Error——MAE)L2 loss-均方误差(Mean Square Error——MSE)Smooth L1 LossMAE、MSE、Smooth L1对比IoU LossGIoU LossDIoU Loss、CIoU LossE-IoU Loss、Focal E-IoU LossReferenceL1 loss-平均绝对误差(Mean Absolute Error——…...
将 Figma 轻松转换为 Sketch 的免费方法
最近浏览网站的时候,发现很多人不知道Figma是怎么转Sketch的。众所周知,Figma支持Sketch文件的导入,但不支持Sketch的导出,那么Figma是如何转Sketch的呢?不用担心,建议使用神器即时设计。它是一个可以实现在…...
GPU推理提速4倍!FlashDecoding++技术加速大模型推理
推理大模型(LLM)是AI服务提供商面临的巨大经济挑战之一,因为运营这些模型的成本非常高。FlashDecoding 是一种新的技术,旨在解决这一问题,它通过提高LLM推理速度和降低成本,为使用大模型赚钱提供了新的可能…...
class类默认导出,header字段在请求中的位置
这是封装好的,没封装的如下 如果没有用uni.post那么就是如下的结构 let header {Content-Type: application/x-www-form-urlencoded,tenant: MDAwMA, } request({url:/sal/formula/validFormula,method:post,data:{},header })...
PHP将pdf转为图片后用OCR识别
1.确保apt包是最新 sudo apt update 2.使用apt安装 sudo apt install tesseract-ocr 3.检查版本 tesseract --version 4.pdf转成图片,这边需要安装imagick插件 $pdf new Imagick(); $pdf->setResolution(150, 150); $pdf->readImage(..$temp); $pdf->…...
IDEA 函数下边出现红色的波浪线,提示报错
Inferred annotations: Method makeOkResult: org.jetbrains.annotations.Contract("_, _, _, _ -> new") org.jetbrains.annotations.NotNull Parameter headers: org.jetbrains.annotations.NotNull 出现这个提示,我应该怎么处理这个函数࿱…...
Discourse 如何在 header 上添加 HTML
虽然现在大部分网站都开始支持使用 CDN 的网站校验了。 但还有些网站在你需要他们提供服务的时候要求使用 header 的 meta 数据校验。 Discourse 是可以轻松的实现上面的功能的。 添加方法 选择你的 Discourse 网站下的自定义。 然后在左侧选择你需要添加的主题。 为了方便…...
[深入理解SSD] 总目录
SSD 综述 [SSD综述 1.1] 导论_SSD让开机击败99%的电脑 [SSD综述 1.2] 固态硬盘(SSD)和机械硬盘(HDD)区别对比介绍? [SSD综述 1.3] SSD及固态存储技术30年简史 [SSD综述 1.4] SSD固态硬盘的结构 [SSD综述 1.5] SSD 主控和固件核心功能详解 [S…...
kubernetes集群编排(7)
目录 k8s认证授权 pod绑定sa 认证 授权 k8s认证授权 pod绑定sa [rootk8s2 ~]# kubectl create sa admin //在当前 Kubernetes 集群中创建一个名为 "admin" 的新服务账户[rootk8s2 secret]# vim pod3.yaml apiVersion: v1 kind: Pod metadata:name: mypod spec…...
学 Simulink——基于 Simulink 的 燃料电池-锂电池混合动力能量流管理
目录 手把手教你学 Simulink 一、引言:为什么需要“混合”?单一能源的困境 二、系统架构:多能流耦合拓扑 三、Step 1:子系统建模(Simulink 实现) A. 燃料电池模型(Simscape Electrical) B. 锂电池模型 C. 负载模型:电机 + 车辆动力学 四、Step 2:能量管理策略…...
Cursor Pro激活器架构深度解析:多平台身份管理系统的设计与实现
Cursor Pro激活器架构深度解析:多平台身份管理系统的设计与实现 【免费下载链接】cursor-free-vip [Support 0.45](Multi Language 多语言)自动注册 Cursor Ai ,自动重置机器ID , 免费升级使用Pro 功能: Youve reached…...
斯坦福MUSK模型:多模态AI在癌症诊疗中的突破与应用
1. 斯坦福MUSK模型:多模态AI如何革新癌症诊疗作为一名长期关注医疗AI应用的从业者,最近斯坦福团队在《Nature》发表的MUSK模型让我眼前一亮。这个基于1亿病理图像和10亿文本数据训练的多模态Transformer,在23项病理学基准测试中全面超越现有模…...
基于Git的RVC模型版本管理:团队协作与模型迭代最佳实践
基于Git的RVC模型版本管理:团队协作与模型迭代最佳实践 你是不是也遇到过这种情况?团队里几个人一起训练RVC模型,今天你改了点训练参数,明天他换了数据集,结果一周后谁也说不清哪个版本的模型效果最好,或者…...
告别C盘爆满!手把手教你自定义Rust安装目录到D盘(附MinGW配置避坑指南)
彻底解放C盘空间:Rust开发环境全链路迁移指南与MinGW深度调优 当你在Windows上安装Rust时,是否注意到.rustup和.cargo目录正悄无声息地吞噬着宝贵的C盘空间?对于128GB SSD系统盘的用户而言,这简直是场灾难。更糟的是,…...
Arduino IDE 2.2.1 + STM32:从C盘迁移库文件到D盘的完整避坑指南
Arduino IDE 2.2.1 STM32:从C盘迁移库文件到D盘的完整避坑指南 对于长期使用Arduino IDE开发STM32项目的工程师来说,C盘空间告急和系统重装后的配置恢复是两大痛点。当你的开发板支持包积累到5GB以上,当你的离线库文件占据大量空间ÿ…...
Qwen3.5-9B-GGUF算法题解题助手:LeetCode风格题目分析与代码生成
Qwen3.5-9B-GGUF算法题解题助手:LeetCode风格题目分析与代码生成 1. 模型能力概览 Qwen3.5-9B-GGUF作为一款开源大语言模型,在算法问题解决方面展现出令人印象深刻的能力。不同于通用聊天模型,它在理解编程题目、分析问题本质和生成正确代码…...
Nest CLI 部署指南:从开发到生产环境的完整流程
Nest CLI 部署指南:从开发到生产环境的完整流程 【免费下载链接】nest-cli CLI tool for Nest applications 🍹 项目地址: https://gitcode.com/gh_mirrors/ne/nest-cli Nest CLI 是一款强大的命令行工具,专为 Nest 应用程序打造&…...
从USB到SATA:手把手拆解PCH芯片如何管理你的电脑外设(以Intel 400系列为例)
从USB到SATA:拆解Intel 400系列PCH芯片的外设管理架构 当你在电脑上插入U盘拷贝文件时,数据究竟经历了怎样的旅程?这个看似简单的操作背后,是Intel平台控制器中枢(PCH)在默默协调着USB控制器、SATA控制器和…...
腾讯面试官问我:“传统 RAG 到底卡在哪?GraphRAG 和 LightRAG 怎么选?”,我震惊:“啥,我刚学RAG,怎么就成传统了”
很多录友看完后反馈:传统 RAG 的那些优化手段确实好用,但有一类问题怎么优化都答不好—— 问"某某文档里提到的某个具体技术细节",RAG 没问题;但问"整个知识库的核心主题是什么"“这几个概念之间有什么关联”…...
