当前位置: 首页 > news >正文

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、背景 最近在做视频转发的开发时&#xff0c;遇到一个问题&#xff0c;前端订阅播放h264视频流时&#xff0c;有时会出现一段时间黑屏&#xff0c;经过测试发现是没有收到关键帧&#xff0c;只有第一帧是关键帧才能保证后续播放正常。所以后端需要实现一个功能&#xff0c;就…...

arcgis 批量删除Table中的某些Field

当shp或者table文件较少时&#xff0c;可以手动删除每个文件中的某些字段&#xff0c;当文件较多时&#xff0c;就需要使用arcpy或者model进行处理。...

工厂设备扫码使用售卖联网开发需要怎么开发开源代码?

我们将详细介绍如何使用开源代码开发一套用于工厂设备联网统计的系统。我们将详细讨论所需硬件组件的选择、开源框架和库的使用、软件开发流程以及最后的集成和部署。在这个过程中&#xff0c;我们将提供实用的操作步骤和指导&#xff0c;帮助你更容易地完成这个复杂的任务。 …...

软考高级之132个工具和技术

分类 工具与技术 描述 数据收集 头脑风暴 在短时间内获得大量创意&#xff0c;适用于团队环境&#xff0c;需要引导者引导&#xff08;过程中可以天马行空&#xff0c;不要打断&#xff09; 包括&#xff1a;头脑风暴、头脑写作 头脑写作&#xff1a;在开始小组创意讨论之…...

算法通过村第十八关-回溯|白银笔记|经典问题

文章目录 前言组合总和问题分割回文串子集问题排序问题字母大小写全排列单词搜索总结 前言 提示&#xff1a;我不愿再给你写信了。因为我终于感到&#xff0c;我们的全部通信知识一个大大的幻影&#xff0c;我们每个人知识再给自己写信。 --安德烈纪德 回溯主要解决一些暴力枚举…...

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 &#x1f44d; 点赞&#xff0c;你的认可是我创作的动力&#xff01; ⭐️ 收藏&#xff0c;你的青睐是我努力的方向&#xff01; ✏️ 评论&#xff0c;你的意见是我进步的财富&#xff01; 应用程序服务器 应用程序服务器是一种用…...

第二十九章 目标检测中的测试模型评价指标(车道线感知)

前言 近期参与到了手写AI的车道线检测的学习中去&#xff0c;以此系列笔记记录学习与思考的全过程。车道线检测系列会持续更新&#xff0c;力求完整精炼&#xff0c;引人启示。所需前期知识&#xff0c;可以结合手写AI进行系统的学习。 介绍 自动驾驶的一大前提是保证人的安全…...

OceanBase 如何通过日志观测冻结转储流程?

本文旨在通过日志解析 OceanBase 的冻结转储流程&#xff0c;以其冻结检查线程为切入点&#xff0c;以租户&#xff08;1002&#xff09;的线程名为例。 作者&#xff1a;陈慧明&#xff0c;爱可生测试工程师&#xff0c;主要参与 DMP 和 DBLE 自动化测试项目。 爱可生开源社区…...

深度图(Depth Map)

文章目录 深度图深度图是什么深度图的获取方式激光雷达或结构光等传感器的方法激光雷达RGB-D相机 双目或多目相机的视差信息计算深度采用深度学习模型估计深度 深度图的应用场景扩展阅读 深度图 深度图是什么 深度图&#xff08;depth map&#xff09;是一种灰度图像&#xf…...

Ubuntu下Anaconda安装

Ubuntu下Anaconda安装 进入anaconda官网 https://www.anaconda.com/ 下载Linux64位版本&#xff1b; 将下载好的".sh"文件放入虚拟机中&#xff1b; 运行指令sudo bash Anaconda3-2023.09-0-Linux-x86_64.sh 此后会自动加载安装程序&#xff0c;中途会停止两次&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 的免费方法

最近浏览网站的时候&#xff0c;发现很多人不知道Figma是怎么转Sketch的。众所周知&#xff0c;Figma支持Sketch文件的导入&#xff0c;但不支持Sketch的导出&#xff0c;那么Figma是如何转Sketch的呢&#xff1f;不用担心&#xff0c;建议使用神器即时设计。它是一个可以实现在…...

GPU推理提速4倍!FlashDecoding++技术加速大模型推理

推理大模型&#xff08;LLM&#xff09;是AI服务提供商面临的巨大经济挑战之一&#xff0c;因为运营这些模型的成本非常高。FlashDecoding 是一种新的技术&#xff0c;旨在解决这一问题&#xff0c;它通过提高LLM推理速度和降低成本&#xff0c;为使用大模型赚钱提供了新的可能…...

class类默认导出,header字段在请求中的位置

这是封装好的&#xff0c;没封装的如下 如果没有用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转成图片&#xff0c;这边需要安装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 出现这个提示&#xff0c;我应该怎么处理这个函数&#xff1…...

Discourse 如何在 header 上添加 HTML

虽然现在大部分网站都开始支持使用 CDN 的网站校验了。 但还有些网站在你需要他们提供服务的时候要求使用 header 的 meta 数据校验。 Discourse 是可以轻松的实现上面的功能的。 添加方法 选择你的 Discourse 网站下的自定义。 然后在左侧选择你需要添加的主题。 为了方便…...

[深入理解SSD] 总目录

SSD 综述 [SSD综述 1.1] 导论_SSD让开机击败99%的电脑 [SSD综述 1.2] 固态硬盘&#xff08;SSD&#xff09;和机械硬盘(HDD)区别对比介绍&#xff1f; [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…...

脑机新手指南(八):OpenBCI_GUI:从环境搭建到数据可视化(下)

一、数据处理与分析实战 &#xff08;一&#xff09;实时滤波与参数调整 基础滤波操作 60Hz 工频滤波&#xff1a;勾选界面右侧 “60Hz” 复选框&#xff0c;可有效抑制电网干扰&#xff08;适用于北美地区&#xff0c;欧洲用户可调整为 50Hz&#xff09;。 平滑处理&…...

【JavaEE】-- HTTP

1. HTTP是什么&#xff1f; HTTP&#xff08;全称为"超文本传输协议"&#xff09;是一种应用非常广泛的应用层协议&#xff0c;HTTP是基于TCP协议的一种应用层协议。 应用层协议&#xff1a;是计算机网络协议栈中最高层的协议&#xff0c;它定义了运行在不同主机上…...

【第二十一章 SDIO接口(SDIO)】

第二十一章 SDIO接口 目录 第二十一章 SDIO接口(SDIO) 1 SDIO 主要功能 2 SDIO 总线拓扑 3 SDIO 功能描述 3.1 SDIO 适配器 3.2 SDIOAHB 接口 4 卡功能描述 4.1 卡识别模式 4.2 卡复位 4.3 操作电压范围确认 4.4 卡识别过程 4.5 写数据块 4.6 读数据块 4.7 数据流…...

【HTML-16】深入理解HTML中的块元素与行内元素

HTML元素根据其显示特性可以分为两大类&#xff1a;块元素(Block-level Elements)和行内元素(Inline Elements)。理解这两者的区别对于构建良好的网页布局至关重要。本文将全面解析这两种元素的特性、区别以及实际应用场景。 1. 块元素(Block-level Elements) 1.1 基本特性 …...

用docker来安装部署freeswitch记录

今天刚才测试一个callcenter的项目&#xff0c;所以尝试安装freeswitch 1、使用轩辕镜像 - 中国开发者首选的专业 Docker 镜像加速服务平台 编辑下面/etc/docker/daemon.json文件为 {"registry-mirrors": ["https://docker.xuanyuan.me"] }同时可以进入轩…...

学校时钟系统,标准考场时钟系统,AI亮相2025高考,赛思时钟系统为教育公平筑起“精准防线”

2025年#高考 将在近日拉开帷幕&#xff0c;#AI 监考一度冲上热搜。当AI深度融入高考&#xff0c;#时间同步 不再是辅助功能&#xff0c;而是决定AI监考系统成败的“生命线”。 AI亮相2025高考&#xff0c;40种异常行为0.5秒精准识别 2025年高考即将拉开帷幕&#xff0c;江西、…...

CSS设置元素的宽度根据其内容自动调整

width: fit-content 是 CSS 中的一个属性值&#xff0c;用于设置元素的宽度根据其内容自动调整&#xff0c;确保宽度刚好容纳内容而不会超出。 效果对比 默认情况&#xff08;width: auto&#xff09;&#xff1a; 块级元素&#xff08;如 <div>&#xff09;会占满父容器…...

视频行为标注工具BehaviLabel(源码+使用介绍+Windows.Exe版本)

前言&#xff1a; 最近在做行为检测相关的模型&#xff0c;用的是时空图卷积网络&#xff08;STGCN&#xff09;&#xff0c;但原有kinetic-400数据集数据质量较低&#xff0c;需要进行细粒度的标注&#xff0c;同时粗略搜了下已有开源工具基本都集中于图像分割这块&#xff0c…...

Java编程之桥接模式

定义 桥接模式&#xff08;Bridge Pattern&#xff09;属于结构型设计模式&#xff0c;它的核心意图是将抽象部分与实现部分分离&#xff0c;使它们可以独立地变化。这种模式通过组合关系来替代继承关系&#xff0c;从而降低了抽象和实现这两个可变维度之间的耦合度。 用例子…...

SQL慢可能是触发了ring buffer

简介 最近在进行 postgresql 性能排查的时候,发现 PG 在某一个时间并行执行的 SQL 变得特别慢。最后通过监控监观察到并行发起得时间 buffers_alloc 就急速上升,且低水位伴随在整个慢 SQL,一直是 buferIO 的等待事件,此时也没有其他会话的争抢。SQL 虽然不是高效 SQL ,但…...