java获取近期视频流关键帧与截图

2023-11-09 13:15

本文主要是介绍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获取近期视频流关键帧与截图的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/376320

相关文章

SpringBoot整合Flowable实现工作流的详细流程

《SpringBoot整合Flowable实现工作流的详细流程》Flowable是一个使用Java编写的轻量级业务流程引擎,Flowable流程引擎可用于部署BPMN2.0流程定义,创建这些流程定义的... 目录1、流程引擎介绍2、创建项目3、画流程图4、开发接口4.1 Java 类梳理4.2 查看流程图4

一文详解如何在idea中快速搭建一个Spring Boot项目

《一文详解如何在idea中快速搭建一个SpringBoot项目》IntelliJIDEA作为Java开发者的‌首选IDE‌,深度集成SpringBoot支持,可一键生成项目骨架、智能配置依赖,这篇文... 目录前言1、创建项目名称2、勾选需要的依赖3、在setting中检查maven4、编写数据源5、开启热

C++中RAII资源获取即初始化

《C++中RAII资源获取即初始化》RAII通过构造/析构自动管理资源生命周期,确保安全释放,本文就来介绍一下C++中的RAII技术及其应用,具有一定的参考价值,感兴趣的可以了解一下... 目录一、核心原理与机制二、标准库中的RAII实现三、自定义RAII类设计原则四、常见应用场景1. 内存管理2. 文件操

Java对异常的认识与异常的处理小结

《Java对异常的认识与异常的处理小结》Java程序在运行时可能出现的错误或非正常情况称为异常,下面给大家介绍Java对异常的认识与异常的处理,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参... 目录一、认识异常与异常类型。二、异常的处理三、总结 一、认识异常与异常类型。(1)简单定义-什么是

SpringBoot项目配置logback-spring.xml屏蔽特定路径的日志

《SpringBoot项目配置logback-spring.xml屏蔽特定路径的日志》在SpringBoot项目中,使用logback-spring.xml配置屏蔽特定路径的日志有两种常用方式,文中的... 目录方案一:基础配置(直接关闭目标路径日志)方案二:结合 Spring Profile 按环境屏蔽关

Java使用HttpClient实现图片下载与本地保存功能

《Java使用HttpClient实现图片下载与本地保存功能》在当今数字化时代,网络资源的获取与处理已成为软件开发中的常见需求,其中,图片作为网络上最常见的资源之一,其下载与保存功能在许多应用场景中都... 目录引言一、Apache HttpClient简介二、技术栈与环境准备三、实现图片下载与保存功能1.

SpringBoot排查和解决JSON解析错误(400 Bad Request)的方法

《SpringBoot排查和解决JSON解析错误(400BadRequest)的方法》在开发SpringBootRESTfulAPI时,客户端与服务端的数据交互通常使用JSON格式,然而,JSON... 目录问题背景1. 问题描述2. 错误分析解决方案1. 手动重新输入jsON2. 使用工具清理JSON3.

java中long的一些常见用法

《java中long的一些常见用法》在Java中,long是一种基本数据类型,用于表示长整型数值,接下来通过本文给大家介绍java中long的一些常见用法,感兴趣的朋友一起看看吧... 在Java中,long是一种基本数据类型,用于表示长整型数值。它的取值范围比int更大,从-922337203685477

java Long 与long之间的转换流程

《javaLong与long之间的转换流程》Long类提供了一些方法,用于在long和其他数据类型(如String)之间进行转换,本文将详细介绍如何在Java中实现Long和long之间的转换,感... 目录概述流程步骤1:将long转换为Long对象步骤2:将Longhttp://www.cppcns.c

SpringBoot集成LiteFlow实现轻量级工作流引擎的详细过程

《SpringBoot集成LiteFlow实现轻量级工作流引擎的详细过程》LiteFlow是一款专注于逻辑驱动流程编排的轻量级框架,它以组件化方式快速构建和执行业务流程,有效解耦复杂业务逻辑,下面给大... 目录一、基础概念1.1 组件(Component)1.2 规则(Rule)1.3 上下文(Conte