Java实现截取视频第一帧的示例详解
目录
- 前言
- 一、通过Java借助第三方库实现
- 1.引用ffmpeg
- 2.引用jcodec
- 二、使用第三方存储自带的方法实现
前言
在实际项目中,会遇到上传视频后,需要截取视频的首帧或指定帧为图片,作为展示使用的需求。这个需求本身并不难,而且网上一搜一大把,今天就针对网上的部分方法做个总结。
一、通过Java借助第三方库实现
1.引用ffmpeg
使用maven,导入pom依赖:
<!-- 操作视频流 --> <dependency> <groupId>org.bytedeco</groupId> <artifactId>javacpp</artifactId> <version>1.4.1</version> </dependency> <dependency> <groupId>org.bytedeco</groupId> <artifactId>javacv</artifactId> <version>1.4.1</version> </dependency> <dependency> <groupId>org.bytedeco.javacpp-presets</groupId> <artifactId>opencv-platform</artifactId> <version>3.4.1-1.4.1</version> </dependency> <dependency> <groupId>org.bytedeco.javacpp-presets</groupId> <artifactId>ffmpeg-platform</artifactId> <version>3.4.2-1.4.1</version> </dependency>
工具类
import org.bytedeco.javacpp.opencv_core; import org.bytedeco.javacpp.opencv_core.IplImage; import org.bytedeco.javacv.FFmpegFrameGrabber; import org.bytedeco.javacv.Frame; import org.bytedeco.javacv.FrameGrabber.Exception; import org.bytedeco.javacv.Java2DFrameConverter; import org.bytedeco.javacv.OpenCVFrameConverter; import javax.imageio.ImageIO; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import lombok.extern.slf4j.Slf4j; /** * 视频操作工具类 */ @Slf4j public class VideoUtils { /** * 截取视频第一帧为图片展示 * * @param filePath 视频路径 * @param targetFilePath 第一帧图片存储位置 * @param targetFileName 图片名称 */ public static void getVideoFirstFrameImage(String filePath, String targetFilePath, String targetFileName) throws Exception { FFmpegFrameGrabber ff = FFmpegFrameGrabber.createDefault(filePath); ff.start(); String rotate = ff.getVideoMetadata("rotate"); Frame f; int i = 0; while (i < 1) { f = ff.grabImage(); IplImage src; 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, targetFilePath, targetFileName); i++; } ff.stop(); } /** * 进行旋转角度操作(为了保证截取到的第一帧图片与视频中的角度方向保持一致) */ public static IplImage rotate(IplImage spythonrc, int angle) { IplImage img = IplImage.create(src.height(), src.width(), src.depth(), src.nChannels()); opencv_core.cvTranspose(src, img); opencv_core.cvFlip(img, img, angle); return img; } public static void doExecuteFrame(Frame f, String targetFilePath, String targetFileName) { if (null == f || null == f.image) { return; } Java2DFrapythonmeConverter converter = new Java2DFrameConverter(); String imageMat = "jpg"; String fileName = targetFilePath + File.separator + targetFileName + "." + imageMat; BufferedImage bi = converter.getBufferedImage(f); File output = new File(fileName); try { ImageIO.write(bi, imageMat, output); } catch (IOException e) { e.printStackTrace(); } } public static void main(String[] args) throws Exception { String url = "C:\\Users\\admin\\Desktop\\test.mp4"; getVideoFirstFrameImage(url, "C:\\Users\\admin\\Desktop", "first"); } }
2.引用jcodec
网上视频截取第一帧的案例还是比较多的,普遍的方法主要是使用ffmpeg对其截取。在实践过程中,发现在自己本地或在window上截取都是成功的。但在linux 环境中截取失败,这里将失败内容贴出来,供他人查看失败原因
# # A fatal error has been detected by the Java Runtime Environment: # # SIGSEGV (0xb) at pc=0x000000000000d1f6, pid=14, tid=0x00007fe8b67e6b10 # # JRE version: OpenJDK Runtime Environment (8.0_212-b04) (build 1.8.0_212-b04) # Java VM: OpenJDK 64-Bit Server VM (25.212-b04 mixed mode linux-amd64 compressed oops) # Derivative: IcedTea 3.12.0 # Distribution: Custom build (Sat May 4 17:33:35 UTC 2019) # Problematic frame: # C 0x000000000000d1f6 # # Core dump written. Default location: //core or core.14 # # If you would like to submit a bug report, please include # instructions on how to reproduce the bug and visit: # https://icedtea.classpath.org/bugzilla # The crash happened outside the Java Virtual MAChine in native code. # See problematic frame for where to report the bug. # --------------- T H R E A D --------------- Current thread (0x00005565ead9c800): JavaThread "http-nio-8889-exec-9" daemon [_thread_in_native, id=109, stack(0x00007fe8b66e6000,0x00007fe8b67e6ad0)] siginfo: si_signo: 11 (SIGSEGV), si_code: 1 (SEGV_MAPERR), si_addr: 0x000000000000d1f6 (省略部分) Stack: [0x00007fe8b66e6000,0x00007fe8b67e6ad0], sp=0x00007fe8b67e1558, free space=1005k Native frames: (J=compiled Java code, j=interpreted, Vv=VM code, C=native code) C 0x000000000000d1f6 Java frames: (J=compiled Java code, j=interpreted, Vv=VM code) j org.bytedeco.javacpp.avformat.avformat_open_input(Lorg/bytedeco/javacpp/avformat$AVFormatContext;Ljava/lang/String;Lorg/bytedeco/javacpp/avformat$AVInputFormat;Lorg/bytedeco/javacpp/avutil$AVDictionary;)I+0 j org.bytedeco.javacv.FFmpegFrameGrabber.startUnsafe()V+624 j org.bytedeco.javacv.FFmpegFrameGrabber.start()V+1 j com.megvii.qingqiu.middle.platform.provider.util.VideoFrameUtil.fetchFrame(Ljava/io/File;Ljava/io/File;)V+26 j com.megvii.qingqiu.middle.platform.provider.util.VideoFrameUtil.fetchFrame(Lorg/springframework/web/multipart/MultipartFile;)Ljava/io/File;+148 j (省略部分)
现在将 ffmpeg 截取的方式改为 jcodec 和 jcodec-javase 来处理。
导入pom依赖:
<dependency> <groupId>org.jcodec</groupId> <artifactId>jcodec</artifactId> <version>0.2.5</version> </dependency> <dependency> <groupId>org.jcodec</groupId> <artifactId>jcodec-javase</artifactId> <version>0.2.5</version> </dependency> <!--<dependency> <groupId>org.bytedeco</groupId> <artifactId>javacv</artifactId> <version>1.4.1</version> </dependency> <dependency> <groupId>org.bytedeco</groupId> <artifactId>javacpp</artifactId> <version>1.4.1</version> </dependency> <dependency> <groupId>org.bytedeco.javacpp-presets</groupId> <artifactId>opencv-platform</artifactId> <version>3.4.1-1.4.1</version> </dependency> <dependency> <groupId>org.bytedeco.javacpp-presets</groupI编程d> <artifactId>ffmpeg-platform</artifactId> <version>3.4.2-1.4.1</version> </dependency>-->
工具类
import lombok.extern.slf4j.Slf4j; import org.apache.commons.io.FileUtils; import org.jcodec.api.FrameGrab; import org.jcodec.api.JCodecException; import org.jcodec.common.model.Picture; import org.jcodec.scale.AWTUtil; import org.springframework.web.multipart.MultipartFile; import javax.imageio.ImageIO; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import java.util.UUID; /** * 视频操作工具类 */ @Slf4j public class VideoUtils { /*** 图片格式*/ private static final String FILE_EXT = "jpg"; /*** 帧数*/ private static final int THUMB_FRAME = 5; /** * 获取指定视频的帧并保存为图片至指定目录 * * @param videoFilePath 源视频文件路径 * @param frameFilePath 截取帧的图片存放路径 */ public static void fetchFrame(String videoFilePath, String frameFilePath) throws Exception { File videoFile = new File(videoFilePath); File frameFile = new File(frameFilePath); getThumbnail(videoFile, frameFile); } /** * 获取指定视频的帧并保存为图片至指定目录 * * @param videoFile 源视频文件 * @param targetFile 截取帧的图片 */ public static void fetchFrame(MultipartFile videoFile, File targetFile) throws Exception { File file = new File(videoFile.getName()); FileUtils.copyInputStreamToFile(videoFile.getInputStream(), file); getThumbnail(file, targetFile); } /** * 获取指定视频的帧并保存为图片至指定目录 * * @param videoFile 源视频文件 */ public static File fetchFrame(MultipartFile videoFile) { String originalFilename = videoFile.getOriginalFilename(); File file = new File(originalFilename); File targetFile = null; try { FileUtils.copyInputStreamToFile(videoFile.getInputStream(), file); int i = originalFilename.lastIndexOf("."); String imageName; if (i > 0) { python imageName = originalFilename.substring(0, i); } else { imageName = UUID.randomUUID().toString().replace("-", ""); } imageName = imageName + ".jpg"; targetFile = new File(imageName); getThumbnail(file, targetFile); } catch (Exception epython) { log.error("获取视频指定帧异常:", e); } finally { if (file.exists()) { file.delete(); } } log.debug("视频文件 - 帧截取 - 处理结束"); return targetFile; } /** * 获取第一帧缩略图 * * @param videoFile 视频路径 * @param targetFile 缩略图目标路径 */ public static void getThumbnail(File videoFile, File targetFile) { try { // 根据扩展名创建一个新文件路径 Picture picture = FrameGrab.getFrameFromFile(videoFile, THUMB_FRAME); BufferedImage bufferedImage = AWTUtil.toBufferedImage(picture); ImageIO.write(bufferedImage, FILE_EXT, targetFile); } catch (IOException | JCodecException e) { e.printStackTrace(); log.error("获取第一帧缩略图异常:", e); } } public static void main(String[] args) { try { long startTime = System.currentTimeMillis(); getThumbnail(new File("C:\\Users\\admin\\Desktop\\test.mp4"), new File("C:\\Users\\admin\\Desktop\\test.jpg")); System.out.println("截取图片耗时:" + (System.currentTimeMillis() - startTime)); } catch (Exception e) { e.printStackTrace(); } } }
二、使用第三方存储自带的方法实现
例如阿里云OSS、华为云OBS
如果我们的视频是上传并保存在第三方服务器上的,那么,我们可以使用阿里提供的视频截帧方法,根据url直接截取视频的指定帧。
具体方法就是在视频的 url 后面,加上一些参数,然后访问这个新的url即可得到指定的截图。
以下是以阿里云OSS为例:
要加入的参数:
"?x-oss-process=video/snapshot,t_0,f_jpg,w_0,h_0,m_fast,ar_auto"
新的url:
"https://gnd.oss-cn-zhangjiakou.aliyuncs.com/vidio/test.map?x-oss-process=video/snapshot,t_0,f_jpg,w_0,h_0,m_fast,ar_auto" (直接访问这个新的url即可得到截图)
参数说明
注意事项:
- 仅支持对视频编码格式为H264和H265的视频文件进行视频截帧。
- OSS默认不保存视频截帧的图片,视频截帧的图片需手动下载并保存至本地。
亲测了一下,主流的视频格式,如:mp4、mov、avi等均可以采用这种方式进行截帧。
具体操作请参见阿里官方文档:阿里云视频截帧操作文档
其它的第三方的云存储跟阿里云的用法,大同小异,根据各自的说明文档,进行开发就行。
以上就是Java实现截取视频第一帧的示例详解的详细内容,更多关于Java截取视频第一帧的资料请关注编程客栈(www.devze.com)其它相关文章!
精彩评论