使用JavaCv的工具类实现流媒体解码存图

文章目录

  • 前言
  • 一、JavaCv是什么?
  • 二、使用步骤
    • 1.引入库
    • 2.获取本地视频的截图并保存
    • 3.获取网络视频流的截图并保存
  • 总结

前言

这两天项目中有个需求是从视频流中定时截图保存至服务器,之前没操作过视频,大致花了半天查网上的资料,做了个可以从视频中截图的demo,仅供参考

一、JavaCv是什么?

JavaCv:是一个工具类,里面封装了对视频进行操作的方法。说JavaCv,就不得不说一下FFmpeg、OpenCV、JavaCPP,我们使用的JavaCv都是对这三个的封装

工具特点java’是否可以直接使用
FFmpegC语言版的本地库
OpenCVC语言版的本地库
JavaCPP对FFmpeg、OpenCV这些常用库进行包装,成为API
JavaCv对JavaCPP进行封装,成为工具类,比JavaCpp更简单易用

我们本次就使用JavaCv来操作视频流,因为JavaCv底层也是使用FFmpeg,内部原理都是一致的,所以可以先学习用JavaCPP操作FFmpeg获取视频流的方式,从中学习原理,便于更好的理解FFmpeg。大佬已经做了很完整的demo攻略,大家可以参考学习:Java版流媒体编解码和图像处理(JavaCPP+FFmpeg)

二、使用步骤

1.引入库

在pom文件中引入包ffmpeg、javacpp、javacv:

<!-- 视频截图 ffmpeg  javacpp  javacv -->
        <dependency>
            <groupId>org.bytedeco.javacpp-presets</groupId>
            <artifactId>ffmpeg-platform</artifactId>
            <version>4.0.2-1.4.3</version>
        </dependency>
        <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>

2.获取本地视频的截图并保存

  • 在D盘创建一个两个文件夹D:\files\video、D:\files\img,在video下放一个MP4格式的视频文件,供截图使用;img下存储的为截图文件,文件名为当前截图时间;获取本地视频文件的第六桢截图保存至本地文件中

代码如下(示例):

package com.example.common.util.video;

import lombok.extern.slf4j.Slf4j;
import org.bytedeco.javacpp.opencv_core;
import org.bytedeco.javacv.*;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * @author sliu
 * @date 2023/6/13 10:26
 **/
@Slf4j
public class VideoUtil {

	public static void main(String[] args) throws IOException {
		getScreenshotByFile(
				"D:\\files\\video\\blue.mp4",
				"D:\\files\\img\\" + new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()) + ".jpg");
	}

	/**
	 * 获取指定视频文件的帧(过滤前5帧)并保存为图片至指定目录
	 * @param videoFile  源视频文件路径
	 * @param imgFile  截取帧的图片存放路径
	 * @throws FrameGrabber.Exception 异常
	 * @throws IOException io异常
	 */
	public static void getScreenshotByFile(String videoFile, String imgFile)
			throws FrameGrabber.Exception, IOException {
		FFmpegFrameGrabber ff = new FFmpegFrameGrabber(videoFile);
		ff.start();
		//视频的旋转角度
		String rotate =ff.getVideoMetadata("rotate");
		int lenght = ff.getLengthInFrames();
		int i = 0;
		Frame f = null;
		while (i < lenght) {
			//过滤前5帧,避免出现全黑的图片,使用grabImage而不是grabFrame,可以避免mov格式的视频截图失败
			f = ff.grabImage();
			if ((i > 5) && (f.image != null)) {
				break;
			}
			opencv_core.IplImage src = null;
			if(null !=rotate &&rotate.length() > 1) {
				OpenCVFrameConverter.ToIplImage converter =new OpenCVFrameConverter.ToIplImage();
				src =converter.convert(f);
				f =converter.convert(rotate(src, Integer.parseInt(rotate)));
			}
			doExecuteFrame(f, imgFile);
			i++;
		}
	}

	/**
	 * 根据视频旋转度来调整图片
	 * @param src 图片
	 * @param angle 旋转角度
	 * @return IplImage	旋转后的图片
	 */
	public static opencv_core.IplImage rotate(opencv_core.IplImage src, int angle) {
		opencv_core.IplImage img = opencv_core.IplImage.create(src.height(),src.width(),src.depth(),src.nChannels());
		opencv_core.cvTranspose(src,img);
		opencv_core.cvFlip(img,img,angle);
		return img;
	}

	/**
	 * 把截取的视频流写入图片中
	 * @param f 截取的视频流
	 * @param targetFileName 目标文件名称
	 */
	public static void doExecuteFrame(Frame f,String targetFileName) {
		if (null ==f ||null ==f.image) {
			return;
		}
		Java2DFrameConverter converter =new Java2DFrameConverter();
		BufferedImage bi =converter.getBufferedImage(f);
		File output =new File(targetFileName);
		try {
			ImageIO.write(bi,"jpg",output);
		}catch (IOException e) {
			e.printStackTrace();
		}
	}
}
  • 运行main方法后,控制台打印如下,代表截图成功

获取本地视频的截图的控制台日志

  • 查看D:\files\img下文件是否存在截图图片

获取本地视频的截图图片

3.获取网络视频流的截图并保存

  • 找一个网络视频流地址(我这属于私人视频流地址,就不放出来了);在D盘创建一个文件夹D:\files\img,存储的为截图文件,文件名为当前截图时间;从该视频流中默认获取第一帧,截图保存到文件中

代码如下(示例):

package com.example.common.util.video;

import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;
import org.bytedeco.javacv.FFmpegFrameGrabber;
import org.bytedeco.javacv.Frame;
import org.bytedeco.javacv.FrameGrabber;
import org.bytedeco.javacv.Java2DFrameConverter;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * @author sliu
 * @date 2023/6/13 10:26
 **/
@Slf4j
public class VideoUtil {

	public static void main(String[] args) throws IOException {
		getScreenshotByUrl(
				"此处为可播放的流媒体地址",
				"D:\\files\\img\\" + new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()) + ".jpg");
	}

	/**
	 * 获取指定视频地址的第一帧并保存为图片至指定目录
	 *
	 * @param url 视频文件路径
	 * @param imgFile 图片保存地址(含文件名)
	 */
	public static void getScreenshotByUrl(String url,String imgFile) throws FrameGrabber.Exception {
		FFmpegFrameGrabber grabber = FFmpegFrameGrabber.createDefault(url);
		try {
			log.debug("截取视频截图开始:,视频地址为:{}" + System.currentTimeMillis(),url);
			grabber.start();
			//设置视频截取帧(默认取第一帧)
			Frame frame = grabber.grabImage();
			Java2DFrameConverter converter = new Java2DFrameConverter();
			//绘制图片
			BufferedImage image = converter.getBufferedImage(frame);
			if(StrUtil.isNotBlank(grabber.getVideoMetadata("rotate"))){
				image = rotate(image, Integer.parseInt(grabber.getVideoMetadata("rotate")));
			}
			try {
				File output =new File(imgFile);
				ImageIO.write(image,"jpg",output);
			}catch (IOException e) {
				e.printStackTrace();
			}
			log.error("视频截图成功");
		} catch (FrameGrabber.Exception e) {
			log.error("视频截图失败{}", e.getMessage());
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			grabber.stop();
			grabber.close();
		}
	}

	/**
	 * 根据视频旋转度来调整图片
	 *
	 * @param src   BufferedImage
	 * @param angel angel	视频旋转度
	 * @return BufferedImage
	 */
	public static BufferedImage rotate(BufferedImage src, int angel) {
		int srcWidth = src.getWidth(null);
		int srcHeight = src.getHeight(null);
		int type = src.getColorModel().getTransparency();
		Rectangle rectDes = calcRotatedSize(new Rectangle(new Dimension(srcWidth, srcHeight)), angel);
		BufferedImage bi = new BufferedImage(rectDes.width, rectDes.height, type);
		Graphics2D g2 = bi.createGraphics();
		g2.translate((rectDes.width - srcWidth) / 2, (rectDes.height - srcHeight) / 2);
		g2.rotate(Math.toRadians(angel), srcWidth / 2, srcHeight / 2);
		g2.drawImage(src, 0, 0, null);
		g2.dispose();
		return bi;
	}

	/**
	 * 计算图片旋转大小
	 *
	 * @param src   Rectangle
	 * @param angel int
	 * @return Rectangle
	 */
	public static Rectangle calcRotatedSize(Rectangle src, int angel) {
		if (angel >= 90) {
			if (angel / 90 % 2 == 1) {
				int temp = src.height;
				src.height = src.width;
				src.width = temp;
			}
			angel = angel % 90;
		}
		double r = Math.sqrt(src.height * src.height + src.width * src.width) / 2;
		double len = 2 * Math.sin(Math.toRadians(angel) / 2) * r;
		double angelAlpha = (Math.PI - Math.toRadians(angel)) / 2;
		double angelDeltaWidth = Math.atan((double) src.height / src.width);
		double angelDeltaHeight = Math.atan((double) src.width / src.height);
		int lenDeltaWidth = (int) (len * Math.cos(Math.PI - angelAlpha - angelDeltaWidth));
		int lenDeltaHeight = (int) (len * Math.cos(Math.PI - angelAlpha - angelDeltaHeight));
		int desWidth = src.width + lenDeltaWidth * 2;
		int desHeight = src.height + lenDeltaHeight * 2;
		return new java.awt.Rectangle(new Dimension(desWidth, desHeight));
	}
}
  • 运行main方法后,控制台打印如下,代表截图成功
    获取网络视频流的截图的控制台日志
  • 查看D:\files\img下文件是否存在截图图片(红框为网络视频流截图图片)
    获取网络视频流的截图图片

总结

以上就是使用JavaCv工具类对流媒体解码存图的demo,大家可以尝试用JavaCpp的方式也做一个demo,可以更好的理解内部原理。当然,真正项目开发上,肯定会偏向使用更简洁代码量更小更通俗易懂的工具类,比如JavaCv。或者大家有什么更好的方式,代码更简洁的方法,也可以分享出来大家一起学习,一起进步!

文章出处登录后可见!

已经登录?立即刷新

共计人评分,平均

到目前为止还没有投票!成为第一位评论此文章。

(0)
xiaoxingxing的头像xiaoxingxing管理团队
上一篇 2023年12月21日
下一篇 2023年12月21日

相关推荐