1.需求背景:

        相机上传的视频,没有上传封面,需要后台对云存储上的视频,添加封面后,保存封面和视频地址在数据库中。

2. JavaCV 简介

JavaCV是对各种常用计算机视觉库的封装后的一组jar包,其中封装了FFmpeg、OpenCV等计算机视觉编程人员常用库的接口,可以通过其中的Utility类方便的在包括Android在内的Java平台上调用这些接口。其中使用最多的应该就是FFmpeg了。 

最开始Javacv是googlecode下面的一个项目,后来迁移到了github,因此JavaCV相关的包名也由com.googlecode.javacv改为org.bytedeco.javacv。项目地址:https://github.com/bytedeco/javacv

3. JavaCV 基本使用指南

从github项目中下载打包好的jar包,其中javacpp.jar 和 javacv.jar是必须的两个包,然后需要什么功能就添加额外的包。例如我需要用到ffmpeg进行视频录制,则需要添加ffmpeg.jar和ffmpeg-android-arm.jar(打包的arm平台so库)。这里很方便的地方是,so库文件都放在了jar文件里面,在集成的时候很简洁明了。 

4.javacv的使用

1.4.x、1.3.x版本的精简jar:

<!--javacv 截取视频封面包-->
        <dependency>
            <groupId>org.bytedeco</groupId>
            <artifactId>javacv</artifactId>
            <version>1.4.3</version>
            <exclusions>
                <exclusion>
                    <groupId>org.bytedeco.javacpp-presets</groupId>
                    <artifactId>*</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>org.bytedeco.javacpp-presets</groupId>
            <artifactId>opencv</artifactId>
            <version>3.4.2-1.4.2</version>
        </dependency>

        <dependency>
            <groupId>org.bytedeco.javacpp-presets</groupId>
            <artifactId>opencv</artifactId>
            <version>3.4.2-1.4.2</version>
              <classifier>windows-x86_64</classifier>
<!--            <classifier>linux-x86_64</classifier>-->
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.bytedeco.javacpp-presets/ffmpeg -->
        <dependency>
            <groupId>org.bytedeco.javacpp-presets</groupId>
            <artifactId>ffmpeg</artifactId>
            <version>4.0.1-1.4.2</version>
        </dependency>

        <dependency>
            <groupId>org.bytedeco.javacpp-presets</groupId>
            <artifactId>ffmpeg</artifactId>
            <version>4.0.1-1.4.2</version>
            <classifier>windows-x86_64</classifier>
<!--            <classifier>linux-x86_64</classifier>-->
        </dependency>
        <!---->

1.5.x版本后的精简包:

<!--javacv 截取视频封面包-->
        <dependency>
            <groupId>org.bytedeco</groupId>
            <artifactId>javacv</artifactId>
            <version>1.5.7</version>
            <exclusions>
                <exclusion>
                    <groupId>org.bytedeco</groupId>
                    <artifactId>*</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>org.bytedeco</groupId>
            <artifactId>ffmpeg</artifactId>
            <version>5.0-1.5.7</version>
        </dependency>

        <dependency>
            <groupId>org.bytedeco</groupId>
            <artifactId>ffmpeg</artifactId>
            <version>5.0-1.5.7</version>
<!--            <classifier>windows-x86_64</classifier>-->
            <classifier>linux-x86_64</classifier>
        </dependency>
        <!---->

2.实现:

package com.camojojo.device.common.utils;

import cn.hutool.core.img.ImgUtil;
import cn.hutool.core.io.FileUtil;
import com.camojojo.common.core.utils.AwsFileUtil;
import lombok.extern.slf4j.Slf4j;
import org.bytedeco.javacv.FFmpegFrameGrabber;
import org.bytedeco.javacv.Frame;
import org.bytedeco.javacv.Java2DFrameConverter;

import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;

/**
 * @author:yuchen
 * @createTime:2022/7/11 16:07
 */

@Slf4j
public class VideoUtil {

    private static final String PATH="/ossRoot/";

    public static void main(String[] args) {
        getVideoPic();
    }

    public static String getVideoPic() {
        String url = "https://xxx/xxxx.mp4";
        String value = "1111123";
        String dir = value +"/";
        String fileName = FileUtil.getName(url);// 100.mp4
        String targetFileName = "thumb_"+fileName.substring(0, fileName.lastIndexOf("."))+".jpg";
        File filePath = new File(PATH+dir);
        if (!filePath.exists()) {
            boolean mkdirs = filePath.mkdirs();
            if(mkdirs){
                log.info("文件夹创建成功:{}",filePath.getAbsolutePath());
            }
        }
        String outPath = PATH+dir+targetFileName;
        File outFile = getScreenshot(url, outPath);
        if(outFile != null){
            String fileName1 = AwsFileUtil.uploadS3Us(outFile, imei, targetFileName);
            System.out.println(fileName1);
            boolean delete = outFile.delete();
            if(delete){
                log.info("删除本地临时文件成功");
            }
            return fileName1;
        }
        return "";
    }




    /**
     * 获取视频封面图
     * @param filePath 视频地址
     * @return map
     */
    private static File getScreenshot(String filePath, String imagePath) {
        log.info("截取视频截图开始:Video={}",filePath);
        filePath = filePath.replaceAll("\\\\","/");
        File output = null;
        FFmpegFrameGrabber grabber = null;
        try{
            grabber  = FFmpegFrameGrabber.createDefault(filePath);
            grabber.start();
            //设置视频截取帧(建议从5帧开始,防止全是黑屏)
            Frame frame = null;
            for (int j = 0; j < 5; j++) {
                frame =  grabber.grabImage();
            }
            //视频旋转度
            String rotate = grabber.getVideoMetadata("rotate");
            Java2DFrameConverter converter = new Java2DFrameConverter();
            //绘制图片
            BufferedImage bi = converter.getBufferedImage(frame);
            if (rotate != null) {
                // 旋转图片
                bi = rotate(bi, Integer.parseInt(rotate));
            }
            //创建文件
            output = new File(imagePath);
            ImgUtil.write(bi, output);
            log.info("截取视频截图成功");
        }catch (Exception e){
            log.error("获取视频封面图,Error:",e);
        }finally {
            if(grabber!=null){
                try { grabber.stop(); } catch (Exception ignored) { }
            }
        }
        return output;
    }


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


    /**
     * @Description: 计算图片旋转大小
     * @param src ""
     * @param angel ""
     * @return  Rectangle
     */
    private 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 angel_alpha = (Math.PI - Math.toRadians(angel)) / 2;
        double angel_dalta_width = Math.atan((double) src.height / src.width);
        double angel_dalta_height = Math.atan((double) src.width / src.height);
        int len_dalta_width = (int) (len * Math.cos(Math.PI - angel_alpha - angel_dalta_width));
        int len_dalta_height = (int) (len * Math.cos(Math.PI - angel_alpha - angel_dalta_height));
        int des_width = src.width + len_dalta_width * 2;
        int des_height = src.height + len_dalta_height * 2;
        return new java.awt.Rectangle(new Dimension(des_width, des_height));
    }

}

5.使用Docker部署后,截屏异常

        在另一个Docker部署的服务中使用该方法,发现一调用,服务就挂。

        最后发现原因是由于那个Docker容器使用的是阉割版的JDK镜像:openjdk:8-jre-alpine ,虽然该镜像能使打包的体积更小,但是容易出现此类的问题,需要换成更完整的JDK镜像。

        后面换成: openjdk:8-jre ,就没问题了。

        javacv版本1.4.x ,会出现物理内存溢出溢出,网上说是旧版本源码bug,升级到1.5.x版本就修复了,今天升级了,运行一段时间再看。

6.1.5.7获取不到视频旋转角度

        改成1.5.6版本正常获取

特此记录。