zoukankan      html  css  js  c++  java
  • Java调用FFmpeg进行视频处理及Builder设计模式的应用


    1、FFmpeg是什么

    FFmpeg(https://www.ffmpeg.org)是一套可以用来记录、转换数字音频、视频,并能将其转化为流的开源计算机程序。它用来干吗呢?视频采集、视频格式转化、视频截图、视频添加水印、视频切片(m3u8、ts)、视频录制、视频推流、更改音视频参数(编码方式、分辨率、码率、比特率等)功能,等等...

    下载下来解压完了呢是这个样子:

    bin中文件夹有个 ffmpeg.exe,点开,是的,一闪而逝并没有什么用,因为ffmpeg靠命令行来调用:

    如上图命令读取 《小星星》.mp4 文件的文件信息,下面的一大片输出就是文件的信息输出了。

    2、Java调用FFmpeg

    现在我们需要把目标视频在第10秒处截图,怎么做呢,先看看用命令行是这样:

    C:workspaceprojectgreejoypicManagerweb	oolsffmpeginffmpeg.exe -i C:UsersDulkDesktopukulele1《小星星》.mp4 -f image2 -ss 10 -t 0.001 -s 320*240 C:UsersDulkDesktopukulele1littleStar.jpg
    其中:
    • C:workspaceprojectgreejoypicManagerweb oolsffmpeginffmpeg.exe 指 ffmpeg.exe 的路径
    • C:UsersDulkDesktopukulele1《小星星》.mp4 指源视频路径
    • C:UsersDulkDesktopukulele1littleStar.jpg 指截图的输出路径
    • -i 表示输入文件的命令
    • -f 表示输出格式,image2表示输出为图片
    • -ss 表示指定的起始位置,这里设置为10,即10s处
    • -t 表示设置录制/转码时长,既然截图就0.001就足够了
    • -s 表示size,设置帧大小

    现在我们看看用Java调用FFmpeg来实现相同的目的,要用到 ProcessBuilder 这个类,该类用于创建操作系统进程,执行本地命令或脚本等工作,其属性command是一个字符串列表(用以输入命令),然后通过start()方法启动执行,但需要注意的是,该方法调用会启动新的进程执行操作,所以并不是和原Java代码同步执行的

    public class FFmpegTest {
    
        public static void main(String[] args) {
    
            String ffmpegExePath = "C:\workspace\project\greejoy\picManager\web\tools\ffmpeg\bin\ffmpeg.exe";
            String inputFilePath = "C:\Users\Dulk\Desktop\ukulele\01\《小星星》.mp4";
            String outputFilePath = "C:\Users\Dulk\Desktop\ukulele\01\littleStarJava.jpg";
    
            List<String> command = new ArrayList<String>();
            command.add(ffmpegExePath);
            command.add("-i");
            command.add(inputFilePath);
            command.add("-f");
            command.add("image2");
            command.add("-ss");
            command.add("10");
            command.add("-t");
            command.add("0.001");
            command.add("-s");
            command.add("320*240");
            command.add(outputFilePath);
    
            ProcessBuilder builder = new ProcessBuilder();
            builder.command(command);
            //正常信息和错误信息合并输出
            builder.redirectErrorStream(true);
            try {
                //开始执行命令
                Process process = builder.start();
    
                //如果你想获取到执行完后的信息,那么下面的代码也是需要的
                StringBuffer sbf = new StringBuffer();
                String line = null;
                BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream()));
                while ((line = br.readLine()) != null) {
                    sbf.append(line);
                    sbf.append(" ");
                }
                String resultInfo = sbf.toString();
                System.out.println(resultInfo);
    
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
    }

    可以看到,拼接命令实际上就是把参数和值按序传入一个List,然后将该List传给 ProcessBuilder 类,然后执行 start() 方法即可。可以看到控制台输出的信息如下:

    需要注意的是:
    • 操作执行信息的读取是分为正常信息和错误信息,分别需要使用 getInputStream() 和 getErrorStream() 读取,上例使用了 builder.redirectErrorStream(true); 将两者合并输出
    • 如果要使用 Process.waitFor() 则需要小心阻塞问题,此处不展开(FFmpeg在JAVA中的使用以及Process.waitFor()引发的阻塞问题

    3、Builder设计模式的应用

    如果你每次使用都要如上例中add()一大堆参数,我估计也是头疼:
    List<String> command = new ArrayList<String>();
    command.add(ffmpegExePath);
    command.add("-i");
    command.add(inputFilePath);
    command.add("-f");
    command.add("image2");
    command.add("-ss");
    command.add("10");
    command.add("-t");
    command.add("0.001");
    command.add("-s");
    command.add("320*240");
    command.add(outputFilePath);

    后来一想,这简直就是绝佳的使用builder模式的示例,先把命令封装成一个类:
    /**
     * FFmpeg命令的封装类
     */
    public class FFmpegCommand {
    
        private List<String> command;
    
        public FFmpegCommand(List<String> command) {
            this.command = command == null ? new ArrayList<String>() : command;
        }
    
        public List<String> getCommand() {
            return command;
        }
    
        public void setCommand(List<String> command) {
            this.command = command;
        }
    
        /**
         * 开始执行命令
         *
         * @param callback 回调
         * @return 命令的信息输出
         * @throws FFmpegCommandException
         */
        public String start(FFmpegCallback callback) throws FFmpegCommandException {
            BufferedReader br = null;
            StringBuffer sbf = new StringBuffer();
            String resultInfo = null;
            try {
                ProcessBuilder builder = new ProcessBuilder();
                builder.command(command);
                //正常信息和错误信息合并输出
                builder.redirectErrorStream(true);
                //开启执行子线程
                Process process = builder.start();
    
                String line = null;
                br = new BufferedReader(new InputStreamReader(process.getInputStream()));
                while ((line = br.readLine()) != null) {
                    sbf.append(line);
                    sbf.append(" ");
                }
                resultInfo = sbf.toString();
    
                //等待命令子线程执行完成
                int exitValue = process.waitFor();
                //完成后执行回调
                if (exitValue == 0 && callback != null) {
                    callback.complete(resultInfo);
                }
                //销毁子线程
                process.destroy();
    
            } catch (IOException e) {
                e.printStackTrace();
                throw new FFmpegCommandException(e.getMessage());
            } catch (InterruptedException e) {
                e.printStackTrace();
                throw new FFmpegCommandException("线程阻塞异常:" + e.getMessage());
            } finally {
                try {
                    if (br != null) {
                        br.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
    
            return resultInfo;
        }
    
    }

    自然,要有builder类:
    public class FFmpegCommandBuilder {
    
        List<String> command = new ArrayList<String>();
    
        public FFmpegCommandBuilder(String exePath) {
            if (exePath == null) {
                throw new FFmpegCommandRuntimeException("ffmpeg.exe 路径不得为空");
            }
            //添加命令的exe执行文件位置
            command.add(exePath);
        }
    
        /**
         * 添加输入文件的路径
         *
         * @param inputFilePath
         */
        public FFmpegCommandBuilder input(String inputFilePath) {
            if (inputFilePath != null) {
                command.add("-i");
                command.add(inputFilePath);
            }
            return this;
        }
    
        /**
         * 添加输出文件的路径
         *
         * @param outputFilePath
         */
        public FFmpegCommandBuilder output(String outputFilePath) {
            if (outputFilePath != null) {
                command.add(outputFilePath);
            }
            return this;
        }
    
        /**
         * 覆盖输出文件
         */
        public FFmpegCommandBuilder override() {
            command.add("-y");
            return this;
        }
    
        /**
         * 强制输出格式
         *
         * @param format 输出格式
         */
        public FFmpegCommandBuilder format(FFmpegCommandFormatEnum format) {
            if (format != null) {
                command.add("-f");
                command.add(format.getValue());
            }
            return this;
        }
    
        /**
         * 设置录制/转码的时长
         *
         * @param duration 形如 0.001 表示0.001秒,hh:mm:ss[.xxx]格式的记录时间也支持
         */
        public FFmpegCommandBuilder duration(String duration) {
            if (duration != null) {
                command.add("-t");
                command.add(duration);
            }
            return this;
        }
    
        /**
         * 搜索到指定的起始时间
         *
         * @param position 形如 17 表示17秒,[-]hh:mm:ss[.xxx]的格式也支持
         */
        public FFmpegCommandBuilder position(String position) {
            if (position != null) {
                command.add("-ss");
                command.add(position);
            }
            return this;
        }
    
        /**
         * 设置帧大小
         *
         * @param size 形如 xxx*xxx
         * @return
         */
        public FFmpegCommandBuilder size(String size) {
            if (size != null) {
                command.add("-s");
                command.add(size);
            }
            return this;
        }
    
        /**
         * 创建FFmpegCommand命令封装类
         *
         * @return FFmpegCommand
         */
        public FFmpegCommand build() {
            return new FFmpegCommand(command);
        }
    
    }

    那么使用builder模式,还是刚才的目的,我们的代码就变成了:
    public class FFmpegBuilderTest {
    
        public static void main(String[] args) {
    
            String ffmpegExePath = "C:\workspace\project\greejoy\picManager\web\tools\ffmpeg\bin\ffmpeg.exe";
            String inputFilePath = "C:\Users\Dulk\Desktop\ukulele\01\《小星星》.mp4";
            String outputFilePath = "C:\Users\Dulk\Desktop\ukulele\01\littleStarJavaBuilder.jpg";
    
            FFmpegCommandBuilder builder = new FFmpegCommandBuilder(ffmpegExePath);
            builder.input(inputFilePath).format(FFmpegCommandFormatEnum.IMAGE)
                   .position("10").duration("0.001").size("320*240").output(outputFilePath);
            FFmpegCommand command = builder.build();
            try {
                String result = command.start(null);
                System.out.println(result);
            } catch (FFmpegCommandException e) {
                e.printStackTrace();
            }
        }
    
    }

  • 相关阅读:
    每天玩转3分钟 MyBatis-Plus
    每天玩转3分钟 MyBatis-Plus
    每天玩转3分钟 MyBatis-Plus
    git仓库管理
    【SpringCloud之pigx框架学习之路 】2.部署环境
    【SpringCloud之pigx框架学习之路 】1.基础环境安装
    Ubuntu 14.04 安装mysql
    Netflix是什么,与Spring Cloud有什么关系
    现学现用-我的第三个小小小私活
    申请微信小游戏账号
  • 原文地址:https://www.cnblogs.com/deng-cc/p/10123069.html
Copyright © 2011-2022 走看看