zoukankan      html  css  js  c++  java
  • 修复com.github.dadiyang-jave-1.0.5获取部分mp4视频高度宽度出现NPM异常

    一、背景

    项目里面使用com.github.dadiyang-jave-1.0.5获取视频的时长、高度和宽度,当获取高度宽度时,会出现NPM异常,相关代码如下

    Encoder videoEncoder = new Encoder();
    File f1 = new File(videoLocalFile);
    MultimediaInfo videoM = videoEncoder.getInfo(f1);
    vo.setDuration((int) videoM.getDuration() / 1000);
    try {
        // 此处获取高度宽度代码可能会抛出NPE异常(某种mp4文件)
        VideoSize videoSize = videoM.getVideo().getSize();
        vo.setMtalHeight(videoSize.getHeight());
        vo.setMtalWidth(videoSize.getWidth());
    }catch (Exception e){
        log.error("待修复,获取视频长度出现异常", e);
    }

    二、问题排查

    获取视频高度宽度原理:执行内置的ffmpeg程序,解析程序输出,得出高度宽度。空指针问题就出在解析程序输出上,对于出现问题的MP4,其在linux下的输出为(windows下不会有这个问题):

    ffmpeg version 4.4 Copyright (c) 2000-2021 the FFmpeg developers
      built with gcc 4.8.5 (GCC) 20150623 (Red Hat 4.8.5-44)
      configuration: --enable-shared --prefix=/usr/local/ffmpeg
      
    
    
      libavutil      56. 70.100 / 56. 70.100
      libavcodec     58.134.100 / 58.134.100
      libavformat    58. 76.100 / 58. 76.100
      libavdevice    58. 13.100 / 58. 13.100
      libavfilter     7.110.100 /  7.110.100
      libswscale      5.  9.100 /  5.  9.100
      libswresample   3.  9.100 /  3.  9.100
    Input #0, mov,mp4,m4a,3gp,3g2,mj2, from '/root/ffmpegs/bad.mp4':
      Metadata:
        major_brand     : mp42
        minor_version   : 0
        compatible_brands: mp42isom
        creation_time   : 2021-09-28T09:44:11.000000Z
      Duration: 00:00:04.95, start: 0.000000, bitrate: 742 kb/s
      Stream #0:0(und): Audio: aac (LC) (mp4a / 0x6134706D), 44100 Hz, mono, fltp, 64 kb/s (default)
        Metadata:
          creation_time   : 2021-09-28T09:44:11.000000Z
          vendor_id       : [0][0][0][0]
      Stream #0:1(und): Video: h264 (High) (avc1 / 0x31637661), yuv420p, 544x960, 707 kb/s, 15.30 fps, 15.33 tbr, 90k tbn, 34 tbc (default)
        Metadata:
          creation_time   : 2021-09-28T09:44:11.000000Z
          vendor_id       : [0][0][0][0]
          encoder         : JVT/AVC Coding

     

    很明显,当ffmpeg解析MP4视频的输出中,音频流的信息只要在视频流前面就会认为误判为音频,导致获取视频信息报错

    三、解决方法

    方法1(未验证):升级版本到1.0.6,但目前由于maven仓库无法引入该依赖,原因如下

    https://github.com/dadiyang/jave/issues/14

    方法2(已验证):自定义解析器,在获取视频信息为空时,使用自定义解析器获取视频高度宽度

    import it.sauronsoftware.jave.DefaultFFMPEGLocator;
    import it.sauronsoftware.jave.VideoSize;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.stereotype.Component;
    
    import java.io.BufferedReader;
    import java.io.File;
    import java.io.IOException;
    import java.io.InputStreamReader;
    import java.lang.reflect.Field;
    import java.util.StringTokenizer;
    import java.util.regex.Matcher;
    import java.util.regex.Pattern;
    
    /**
     * 视频信息解析器(为简单起见,只获取宽高)
     * 由于{@link it.sauronsoftware.jave.Encoder}判断视频宽度高度有bug,可能将视频判断为音频,导致获取不到视频的宽高,影响视频的裁剪
     * @author zhangyj
     */
    @Slf4j
    @Component
    public class VideoEncoder {
    
        private static final Pattern SIZE_PATTERN = Pattern.compile("(\d+)x(\d+)", Pattern.CASE_INSENSITIVE);
    
        private static final Pattern VIDEO_PATTERN = Pattern.compile("^\s*Stream #\S+: ((?:Video)): (.*)\s*$", Pattern.CASE_INSENSITIVE);
    
        private String programPath;
    
        {
            try {
                this.programPath = getFfmepgProgramPath();
            } catch (Exception e) {
                log.error("获取ffmpeg程序路径失败", e);
            }
        }
    
        public VideoSize getVideoSize(File source) throws Exception {
            String[] commands = {programPath, "-i",  source.getAbsolutePath()};
            try (BufferedReader reader = getCommandReader(commands)){
                return getVideoSize(reader);
            }
        }
    
        private String getFfmepgProgramPath() throws Exception {
            DefaultFFMPEGLocator locator = new DefaultFFMPEGLocator();
            // 通过反射获取获取ffmpeg程序绝对路径,该属性未提供get方法
            Field field = locator.getClass().getDeclaredField("path");
            field.setAccessible(true);
            return String.valueOf(field.get(locator));
        }
    
        private BufferedReader getCommandReader(String[] commands) throws IOException {
            Runtime runtime = Runtime.getRuntime();
            Process exec = runtime.exec(commands);
            //noinspection AlibabaAvoidManuallyCreateThread
            runtime.addShutdownHook(new Thread(exec::destroy));
            return new BufferedReader(new InputStreamReader(exec.getErrorStream()));
        }
    
        private VideoSize getVideoSize(BufferedReader reader) throws Exception{
            while (true) {
                String line = reader.readLine();
                if (line == null) {
                    break;
                }
                Matcher videoMatcher = VIDEO_PATTERN.matcher(line);
                if (!videoMatcher.matches()) {
                    continue;
                }
                StringTokenizer st = new StringTokenizer(videoMatcher.group(2), ",");
                for (int i = 0; st.hasMoreTokens(); i++) {
                    String token = st.nextToken().trim();
                    if(i == 0){
                        continue;
                    }
                    Matcher sizeMatcher = SIZE_PATTERN.matcher(token);
                    if (!sizeMatcher.find()) {
                        continue;
                    }
                    return new VideoSize(Integer.parseInt(sizeMatcher.group(1)), Integer.parseInt(sizeMatcher.group(2)));
                }
            }
            return null;
        }
    
    }

    使用代码修改为:

    Encoder vEncoder = new Encoder();
    File f1 = new File(videoLocalFile);
    MultimediaInfo videoM = vEncoder.getInfo(f1);
    vo.setDuration((int) videoM.getDuration() / 1000);
    VideoInfo videoInfo = videoM.getVideo();
    VideoSize videoSize = videoInfo != null? videoInfo.getSize():videoEncoder.getVideoSize(f1);
    if(videoSize != null){
        vo.setMtalHeight(videoSize.getHeight());
        vo.setMtalWidth(videoSize.getWidth());
    }
    欢迎各位留言
  • 相关阅读:
    mysql 查询优化 ~ select count 知多少
    mongodb 案例 ~ 经典故障案例
    printk 驱动调试
    21天学通C++学习笔记(七):函数
    OPC UA
    MQTT
    分库分表
    水平、垂直权限问题(横向越权与纵向越权)
    数据库中的行转列和列转行
    面试知识点
  • 原文地址:https://www.cnblogs.com/zhangyuejia/p/15492387.html
Copyright © 2011-2022 走看看