ffmpeg安装及在java中的使用案例

前言

在开发中,经常有项目页面需要播放视频,也经常会有视频的容量很大,上次客户需要在页面上播放他们公司的宣传片,3分钟的视频,足足有1个G,测试直接页面上播放的话是很卡的,几乎无法观看,最后只能用软件压缩到100多MB才能勉强观看,后面知道了ffmpeg的视频播放方式,觉得这种方式在项目很实用,所以写一个测试demo,方便以后在项目中使用。

什么是ffmpeg?

FFmpeg是一套可以用来记录、转换数字音频、视频,并能将其转化为流的开源计算机程序。采用LGPL或GPL许可证。它提供了录制、转换以及流化音视频的完整解决方案。它包含了非常先进的音频/视频编解码库libavcodec,为了保证高可移植性和编解码质量,libavcodec里很多code都是从头开发的。

Windows下载及安装

  1. 打开官方链接:官方链接
    在这里插入图片描述

  2. 点击“Windows builds by Zeranoe”在这里插入图片描述

  3. 点击“download”即可下载
    在这里插入图片描述

  4. 解压下载的压缩包到任意位置,可以看到压缩包里面有个“bin”文件夹,这个就是我们需要配置进Windows环境变量的文件夹地址
    在这里插入图片描述

  5. 配置环境变量,配置过程和配置java环境变一样,win7下,右键“我的电脑”->点击左侧“高级系统设置”->点击右下方“环境变量”出现以下对话框
    在这里插入图片描述

  6. 运行cmd命令,在控制台输入命令“ffmpeg -version”或者“ffmpeg”出现以下信息,则说明安装成功
    在这里插入图片描述

后台代码

注: 下面有一个utils类,一个controller类,一个上传图片页面,一个播放视频页面。
基本逻辑是:
浏览器打开上传页面,上传视频到本地,在controller接口中有转化格式代码(调用的utils类),会执行视频格式转化,然后再在播放页面里面修改视频地址,再打开播放页面,就能看到效果了。

  1. utils类,里面参数包括ffmpeg.exe的目录,每个ts的时间之类的,为了好理解都是用成员变量写死的,在实际开发中可以写在properties文件中,用@Value注解获取,方便修改
package com.mytest.test.utils;

import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;

/**
 *  mp4转换m3u8工具类
 */
@Component
public class ConvertM3U8Util {
    // ffmpeg.exe的目录
    private String ffmpegpath = "E:/ffmpeg/bin/ffmpeg.exe";
    //每个ts的时间
    private String time = "10";
    //视频分辨率
    private String resolution = "1280x720";

    public boolean convertOss(String folderUrl,String fileName){
        if (!checkfile(folderUrl + fileName)){
            System.out.println("文件不存在!");
            return false;
        }
        //验证文件后缀
        String suffix = StringUtils.substringAfter(fileName, ".");
        String fileFullName = StringUtils.substringBefore(fileName, ".");
        if (!validFileType(suffix)){
            return false;
        }
        return processM3U8(folderUrl,fileName,fileFullName,resolution,time);
    }

    /**
     * 验证上传文件后缀
     * @param type
     * @return
     */
    private boolean validFileType ( String type ) {
        if ("mp4".equals(type)){
            return true;
        }
        return false;
    }

    /**
     * 验证是否是文件格式
     * @param path
     * @return
     */
    private boolean checkfile(String path) {
        File file = new File(path);
        if (!file.isFile()) {
            return false;
        } else {
            return true;
        }
    }

    // ffmpeg能解析的格式:(asx,asf,mpg,wmv,3gp,mp4,mov,avi,flv等)

    /**
     * ffmpeg程序转换m3u8
     * @param folderUrl
     * @param fileName
     * @param fileFullName
     * @return
     */
    private boolean processM3U8(String folderUrl,String fileName, String fileFullName) {
        //这里就写入执行语句就可以了
        List commend = new java.util.ArrayList();
        commend.add(ffmpegpath);
        commend.add("-i");
        commend.add(folderUrl+fileName);
        commend.add("-c:v");
        commend.add("libx264");
        commend.add("-hls_time");
        commend.add("20");
        commend.add("-hls_list_size");
        commend.add("0");
        commend.add("-c:a");
        commend.add("aac");
        commend.add("-strict");
        commend.add("-2");
        commend.add("-f");
        commend.add("hls");
        commend.add(folderUrl+ fileFullName +".m3u8");
        try {
            ProcessBuilder builder = new ProcessBuilder();//java
            builder.command(commend);
            Process p = builder.start();
            int i = doWaitFor(p);
            System.out.println("------>"+i);
            p.destroy();
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     *
     * @param folderUrl
     * @param fileName
     * @param fileFullName
     * @param resolution 分辨率
     * @param time 每个ts时长
     * @return
     */
    private boolean processM3U8(String folderUrl,String fileName, String fileFullName,String resolution,String time){
        //这里就写入执行语句就可以了
        List commend = new java.util.ArrayList();
        commend.add(ffmpegpath);
        commend.add("-i");
        commend.add(folderUrl+fileName);
        commend.add("-profile:v");
        commend.add("baseline");
        commend.add("-level");
        commend.add("3.0");
        commend.add("-s");
        commend.add(resolution);
        commend.add("-start_number");
        commend.add("0");
        commend.add("-hls_time");
        commend.add(time);
        commend.add("-hls_list_size");
        commend.add("0");
        commend.add("-f");
        commend.add("hls");
        commend.add(folderUrl+ fileFullName +".m3u8");
        try {
            //java
            ProcessBuilder builder = new ProcessBuilder();
            builder.command(commend);
            Process p = builder.start();
            int i = doWaitFor(p);
            System.out.println("------>"+i);
            p.destroy();
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    private boolean processM3U8(String ffmpegpath,String folderUrl,String fileName, String fileFullName,String resolution,String time){
        //这里就写入执行语句就可以了
        List commend = new java.util.ArrayList();
        commend.add(ffmpegpath);
        commend.add("-i");
        commend.add(folderUrl+fileName);
        commend.add("-profile:v");
        commend.add("baseline");
        commend.add("-level");
        commend.add("3.0");
        commend.add("-s");
        commend.add(resolution);
        commend.add("-start_number");
        commend.add("0");
        commend.add("-hls_time");
        commend.add(time);
        commend.add("-hls_list_size");
        commend.add("0");
        commend.add("-f");
        commend.add("hls");
        commend.add(folderUrl+ fileFullName +".m3u8");
        try {
            //java
            ProcessBuilder builder = new ProcessBuilder();
            builder.command(commend);
            Process p = builder.start();
            int i = doWaitFor(p);
            System.out.println("------>"+i);
            p.destroy();
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    /**
     * 监听ffmpeg运行过程
     * @param p
     * @return
     */
    public int doWaitFor(Process p) {
        InputStream in = null;
        InputStream err = null;
        int exitValue = -1; // returned to caller when p is finished
        try {
            System.out.println("comeing");
            in = p.getInputStream();
            err = p.getErrorStream();
            boolean finished = false; // Set to true when p is finished

            while (!finished) {
                try {
                    while (in.available() > 0) {
                        Character c = new Character((char) in.read());
                        System.out.print(c);
                    }
                    while (err.available() > 0) {
                        Character c = new Character((char) err.read());
                        System.out.print(c);
                    }

                    exitValue = p.exitValue();
                    finished = true;

                } catch (IllegalThreadStateException e) {
                    Thread.currentThread().sleep(500);
                }
            }
        } catch (Exception e) {
            System.err.println("doWaitFor();: unexpected exception - "
                    + e.getMessage());
        } finally {
            try {
                if (in != null) {
                    in.close();
                }

            } catch (IOException e) {
                System.out.println(e.getMessage());
            }
            if (err != null) {
                try {
                    err.close();
                } catch (IOException e) {
                    System.out.println(e.getMessage());
                }
            }
        }
        return exitValue;
    }

    public static void main(String[] args) {
        ConvertM3U8Util util = new ConvertM3U8Util();
        boolean b = util.processM3U8("D:/ffmpeg/bin/ffmpeg.exe","d:/", "123.mp4", "video-s", "1280x720", "10");
        System.out.println(b);
    }

}

其中thumbnailator依赖

        <dependency>
            <groupId>net.coobird</groupId>
            <artifactId>thumbnailator</artifactId>
            <version>0.4.8</version>
        </dependency>
  1. 测试controller类
@Controller
@RequestMapping("/upload")
public class UploadController {
	//视频转换后的地址
    private static final String TMP_PATH = "D:/test/tmp";
    @Autowired
    private ConvertM3U8Util convertM3U8Util;

    @ResponseBody
    @PostMapping("/upload")
    public Result fileUpload(@RequestParam("uploadFile") MultipartFile file) throws  Exception {
        if (file.isEmpty()) {
            return Result.fail("文件不能为空");
        }
        try {
            File tmp = new File(TMP_PATH, file.getOriginalFilename());
            if (!tmp.getParentFile().exists()) {
                tmp.getParentFile().mkdirs();
            }
            String[] fileInfo = getFileInfo(tmp);
            File orRenameFile = createOrRenameFile(tmp, fileInfo[0], fileInfo[1]);
            if (tmp.renameTo(orRenameFile)) {
                file.transferTo(orRenameFile);
            }else {
                file.transferTo(tmp);
            }
            if(!convertM3U8Util.convertOss(TMP_PATH+'/', file.getOriginalFilename())){
                return Result.fail("上传失败");
            }
            return Result.suc("success");
        } catch (IOException e) {
            return Result.fail("文件不能为空");
        }

    }
    
    /**
     * 创建或重命名文件
     * ps:sss.jpg    sss(1).jpg
     * @param from
     * @param toPrefix
     * @param toSuffix
     * @return
     */
    public static File createOrRenameFile(File from, String toPrefix, String toSuffix) {
        File directory = from.getParentFile();
        if (!directory.exists()) {
            if (directory.mkdir()) {
                System.out.println("Created directory " + directory.getAbsolutePath());
            }
        }
        File newFile = new File(directory, toPrefix + toSuffix);
        for (int i = 1; newFile.exists() && i < Integer.MAX_VALUE; i++) {
            newFile = new File(directory, toPrefix + "(" + i + ")" + toSuffix);
        }
        if (!from.renameTo(newFile)) {
            System.out.println("Couldn't rename file to " + newFile.getAbsolutePath());
            return from;
        }
        return newFile;
    }

    /**
     * 获取File的   . 前后字串
     * @param from
     * @return
     */
    public static String[] getFileInfo(File from) {
        String fileName = from.getName();
        int index = fileName.lastIndexOf(".");
        String toPrefix = "";
        String toSuffix = "";
        if (index == -1) {
            toPrefix = fileName;
        } else {
            toPrefix = fileName.substring(0, index);
            toSuffix = fileName.substring(index, fileName.length());
        }
        return new String[]{toPrefix, toSuffix};
    }
}
  1. 上传图片页面(测试用的Layui配置的粗糙版本)
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
    <title>开始使用layui</title>
    <link rel="stylesheet" href="../layui/css/layui.css">
</head>
<body>

<!-- 你的HTML代码 -->
<button type="button" class="layui-btn" id="test5"><i class="layui-icon"></i>上传视频</button>
<script src="/layui/layui.js"></script>
<script>
    //一般直接写在一个js文件中
    layui.use(['layer', 'form','upload'], function(){
        var layer = layui.layer
            , form = layui.form
            , upload = layui.upload;
        upload.render({
            elem: '#test5'
            ,url: 'http://localhost:8080/upload/upload' //改成您自己的上传接口
            ,accept: 'video' //视频
            ,field: 'uploadFile'
            ,method: 'post'
            ,done: function(res){
                layer.msg('上传成功');
                console.log(res)
            }
        });
    });
</script>
</body>
</html>
  1. 播放视频页面,播放视频的地址换上自己的地址就可以了,其他的引入不需要换
<!DOCTYPE html>
<html lang="en">
<head>
    <link href="https://unpkg.com/video.js/dist/video-js.css" rel="stylesheet">
    <script src="https://unpkg.com/video.js/dist/video.js"></script>
    <script src="https://unpkg.com/videojs-contrib-hls/dist/videojs-contrib-hls.js"></script>
</head>
<body>
<video id="my_video_1" class="video-js vjs-default-skin" controls preload="auto"
       data-setup='{}'>
    <!-- 这里写的地址是测试生成的视频地址,只做测试用,实际开发要根据业务从后台获取 -->
    <source type="application/x-mpegURL" src="http://localhost:8080/video/neihan1.m3u8">
    <!-- video.js给的示例 -->
    <!--<source src="http://d2zihajmogu5jn.cloudfront.net/bipbop-advanced/bipbop_16x9_variant.m3u8" type="application/x-mpegURL">-->
</video>
<script src="//cdn.bootcss.com/video.js/7.0.0-alpha.1/video.min.js"></script>
</body>
</html>

另外需要注意的是,视频转换后的地址其实是在“D:\test\tmp”里面,但是上面却可以用“localhost:8080/video/neihan1.m3u8”这样的链接打开,要实现这点的话,需要在项目里面配置虚拟地址映射,贴上代码:

@Configuration
public class WebConfigurer implements WebMvcConfigurer {
    @Autowired
    private JwtInterceptor jwtInterceptor;

    private String videoAdd = "D:/test/tmp/";

    // 这个方法是用来配置静态资源的,比如html,js,css,等等
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/**").addResourceLocations("classpath:/static/");
        registry.addResourceHandler("/video/**").addResourceLocations("file:" + videoAdd);
    }
}

测试上传及播放

  1. 上传视频,准备的这个视频大小是110MB左右
    在这里插入图片描述
  2. 看控制台信息
    在这里插入图片描述
  3. 查看本地文件夹是否有上述文件
    在这里插入图片描述
  4. 打开视频播放页面
    在这里插入图片描述
  5. 打开testVideo.m3u8文件看看
    在这里插入图片描述
  6. 测试拉进度条
    在这里插入图片描述

总结

这种视频播放模式,在播放时间比较长或者容量比较大的视频时有显著的优势,根据自己电脑的带宽不断的拉取后面的数据,在拉进度条之后的加载速度也比普通模式快很多,所以在需要播放视频的项目中,可以考虑用这种模式去实现,这个案例是在windows中实现的,如果要放到服务器的话,需要下载Linux版本的ffmpeg。
因为测试,所以上面的utils类里面都是用的“System.out.println()”,因为在生成环境中不建议使用“System.out.println()”,记得改成“log.info()”,那么问题来了:为什么不建议在生产环境中使用“System.out.println()”呢?
如果不知道答案,可以自行点开查看“System.out.println()”的底层实现,相信一眼就能看出来答案!

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值