修复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());
}

 

posted @ 2021-11-01 10:59  乐佳Jcode  阅读(509)  评论(0编辑  收藏  举报