说明:

ffmpeg用于视频编码、视频解码。

一、ffmpeg安装/使用(windows系统):

1.ffmpeg安装:

(1)下载ffmpeg-4.3.2-2021-02-27-essentials_build.zip,解压到C:\ffmpeg:

https://www.gyan.dev/ffmpeg/builds/packages/ffmpeg-4.3.2-2021-02-27-essentials_build.zip

(2)配置ffmpeg环境变量,在系统变量”Path”末尾加入路径:

…;C:\ffmpeg\bin;

2.ffmpeg一些命令使用(打开CMD):

(1)查看版本号:

ffmpeg.exe -version

(2)转化视频文件格式(这里为avi转mp4,也可转为mp3、gif等):

<1>说明:

命令说明:

-c:v  # -c:v设置编码格式为libx264(h264的一种)
-s    # 设置显示分辨率为1280x720
-pix_fmt yuv420p   # 设置每个像素点采样方式(分三种:yuv4:4:4、yuv4:2:2、yuv4:2:0),用于还原每个像素点的yuv值(亮度与色彩)。
-b:a  # 设置音频码率为63k
-b:v  # 设置视频码率为753k
-r    # 设置帧率为18

Kbps码率计算公式:
10MB视频:10 * 1024 * 1024 * 8 /10 / 1024 = 8192Kbps

分辨率要求:
高清>=960x400、超清>=1280x544、1080p>=1920x800

时长要求:
高清/超清/1080p>=15秒

平均码率要求:
H.265/HEVC(mp4格式):高清>0.56Mbps、超清>0.9Mbps、1080p>2.1Mbps
H.264/AVC/AVCHD/X264(mp4、mkv格式)、RV40/RealVideo 9(rmvb格式)、WMV3/WVC1/WMVA/VC-1/Windows Meadia Video 9(wmv格式):高清>0.86Mbps、超清>1.5Mbps、1080p>3.5Mbps
MPEG-4 Visual/Xvid/Divx(avi、mp4格式):高清>1.6Mbps、超清>3Mbps、1080p>5Mbps
MPEG-2(mpeg/mpg/vob格式)、MPEG-1(mpeg/mpg格式):高清>4Mbps、超清>7.5Mbps、1080p>8Mbps

<2>命令(cd到C:\video目录下,old.avi在此目录):

ffmpeg.exe -i old.avi -c:v libx264 -s 1280x720 -pix_fmt yuv420p -b:a 63k -b:v 753k -r 18 old1.mp4

(3)将mp4分段,生成一个m3u8索引文件+n个ts文件:

说明:

-hls_time      >设置每片长度为10秒
-hls_list_size >设置保存分段数量,0保存所有
-hls_segment_filename  >设置分段文件名,%06d动态生成6位数字,如new_123456.ts

命令(cd到C:\video目录下,old1.mp4在此目录,生成的m3u8和ts列表放在C:\hls目录下):

ffmpeg -i old1.mp4 -hls_time 10 -hls_list_size 0 -hls_segment_filename ../hls/new_%06d.ts ../hls/new.m3u8

生成的文件如下:

new.m3u8
new_000000.ts
new_000001.ts
new_000002.ts
...

new.m3u8内容如下:

#EXTM3U                       //开始标志
#EXT-X-VERSION:3              //版本号
#EXT-X-TARGETDURATION:14
#EXT-X-MEDIA-SEQUENCE:0
#EXTINF:13.888889,
new_000000.ts                 //每个片段的名称
#EXTINF:13.888889,
new_000001.ts
...
#EXT-X-ENDLIST                //结束标志

二、使用java调用ffmpeg转换视频编码:

1.在工程application.yml中配置ffmpeg.exe路径和源视频与生成的视频目录:


#hls:
hls: #自定义属性,配置ffmpeg与视频加载与输出的地址
  ffmpeg‐path: C:/ffmpeg/bin/ffmpeg.exe
  avi-dir: C:/video/avi/
  mp4-dir: C:/video/mp4/
  hls-dir: C:/video/hls/

2.调用ffmpeg进行视频转换的java类:

(1)处理基类,VideoConvertBase.java:

//视频处理基类
public abstract class VideoConvertBase {
    protected String ffmpegPath; //ffmpeg.exe路径
    public VideoConvertBase(String ffmpegPath) {
        this.ffmpegPath = ffmpegPath;
    }
    //删除已存在的文件
    protected void delete(String dirPath, String fileName) {
        if (dirPath == null || fileName == null) {
            return;
        }
        File file = new File(dirPath, fileName);
        if (file.exists() && file.isFile()) {
            file.delete();
        }
    }
    //校验视频长度是否一样
    protected Boolean check(String oldVideo, String newVideo) {
        if (oldVideo == null || newVideo == null) {
            return false;
        }
        String oldLen = getLenth(oldVideo);
        if (oldLen == null) {
            return false;
        }
        if (oldLen.lastIndexOf(".") != -1 && oldLen.length() > 1) {
            oldLen = oldLen.substring(0, oldLen.lastIndexOf("."));  //视频分秒
        }
        String newLen = getLenth(newVideo);
        if (newLen == null) {
            return false;
        }
        if (newLen.lastIndexOf(".") != -1 && newLen.length() > 1) {
            newLen = newLen.substring(0, newLen.lastIndexOf("."));  //视频分秒
        }
        if (!oldLen.equals(newLen)) {
            return false;
        }
        return true;
    }
    //获取视频长度
    public String getLenth(String path) {
        ArrayList<String> cmd = new ArrayList<>(); //等同于cmd中输入命令:ffmpeg -i old.avi
        cmd.add(ffmpegPath);
        cmd.add("-i");
        cmd.add(path);
        try {
            Process process = new ProcessBuilder().command(cmd).redirectErrorStream(true).start(); //执行命令
            String info = readInfo(process);
            int start = info.trim().indexOf("Duration: ");
            int end = info.trim().indexOf(", start:");
            if (start == -1 || end == -1) {
                return null;
            }
            String time = info.substring(start + 10, end);
            if (time != null && !time.equals("")) {
                return time.trim();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
    //读取信息
    public String readInfo(Process process) {
        InputStream in = null;
        try {
            StringBuffer sb = new StringBuffer();
            in = process.getInputStream();
            InputStream errorIn = process.getErrorStream();
            boolean done = false;
            int maxSleep = 10 * 60;
            int sleep = 0;
            while (!done) {
                if (sleep > maxSleep) {
                    return "读取错误";
                }
                try {
                    while (in.available() > 0) {
                        sb.append(new Character((char) in.read()));
                    }
                    while (errorIn.available() > 0) {
                        sb.append(new Character((char) in.read()));
                    }
                    process.exitValue();
                    done = true;
                } catch (IllegalThreadStateException e) {
                    e.printStackTrace();
                    Thread.currentThread().sleep(1000);
                    sleep++;
                }
            }
            return sb.toString();
        } catch (Exception e) {
            e.printStackTrace();
            return "读取错误";
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

(2)avi转mp4的类:


public class AviToMp4Biz extends VideoConvertBase {
    private static AviToMp4Biz I;
    private AviToMp4Biz(String ffmpegPath) {
        super(ffmpegPath);
    }
    public static AviToMp4Biz I(String ffmpegPath) {
        if (I == null) {
            I = new AviToMp4Biz(ffmpegPath);
        }
        return I;
    }
    private String createMp4Name(String mp4Name) {
        return mp4Name.substring(0, mp4Name.lastIndexOf(".")) + ".mp4";
    }
    //avi转mp4
    public String aviToMp4(String aviDir, String aviName, String mp4Dir) {
        String mp4Name = createMp4Name(aviName);
        String aviPath = aviDir + aviName;
        String mp4Path = mp4Dir + mp4Name;
        delete(mp4Dir, mp4Name);  //删已存在的
        File file = new File(mp4Path);
        if(!file.exists()){
            try {
                file.createNewFile();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        //等同在cmd中输入:ffmpeg.exe -i C:/video/avi/old.avi -c:v libx264 -y -s 1280x720 -pix_fmt yuv420p -b:a 63k -b:v 753k -r 18 C:/video/mp4/old.mp4
        ArrayList<String> cmd = new ArrayList<>();
        cmd.add(ffmpegPath); //配置ffmpeg.exe
        cmd.add("-i");
        cmd.add(aviPath);  //avi文件路径
        cmd.add("-c:v");
        cmd.add("libx264");
        cmd.add("-y");
        cmd.add("-s");
        cmd.add("1280x720");
        cmd.add("-pix_fmt");
        cmd.add("yuv420p");
        cmd.add("-b:a");
        cmd.add("63k");
        cmd.add("-b:v");
        cmd.add("753k");
        cmd.add("-r");
        cmd.add("18");
        cmd.add(mp4Path);  //mp4文件路径
        try {
            new ProcessBuilder().command(cmd).redirectErrorStream(true).start(); //执行avi转mp4命令
            if (check(aviPath, mp4Path)) {
                return "avi转mp4成功";
            } else {
                return "avi转mp4失败,视频文件校验不通过";
            }
        } catch (IOException e) {
            e.printStackTrace();
            return "avi转mp4异常: " + e.getMessage();
        }
    }
}

(3)mp4转hls的类:


public class MP4ToHlsBiz extends VideoConvertBase {
    private static MP4ToHlsBiz I;
    private MP4ToHlsBiz(String ffmpegPath) {
        super(ffmpegPath);
    }
    public static MP4ToHlsBiz I(String ffmpegPath) {
        if (I == null) {
            I = new MP4ToHlsBiz(ffmpegPath);
        }
        return I;
    }
    private String createM3u8Name(String mp4Name) {
        return mp4Name.substring(0, mp4Name.lastIndexOf(".")) + ".m3u8";
    }
    //mp4转hls
    public String mp4ToHls(String mp4Dir, String mp4Name, String hlsDir) {
        String m3u8Name = createM3u8Name(mp4Name);
        String mp4Path = mp4Dir + mp4Name;
        String hlsPath = hlsDir + m3u8Name;
        delete(hlsDir, m3u8Name);  //删已存在的
        //等同在cmd中输入:ffmpeg -i C:/video/mp4/old.mp4 -hls_time 10 -hls_list_size 0 -hls_segment_filename C:/video/hls/old_%06d.ts C:/video/hls/old.m3u8
        List<String> cmd = new ArrayList<String>();
        cmd.add(ffmpegPath);
        cmd.add("-i");
        cmd.add(mp4Path);
        cmd.add("-hls_time");
        cmd.add("10");
        cmd.add("-hls_list_size");
        cmd.add("0");
        cmd.add("-hls_segment_filename");
        cmd.add(hlsDir + m3u8Name.substring(0, m3u8Name.lastIndexOf(".")) + "_%06.ts"); //等同:../hls/new_%06d.ts
        cmd.add(hlsPath);
        try {
            new ProcessBuilder().command(cmd).redirectErrorStream(true).start(); //执行mp4转hls命令
            if (!check(mp4Path, hlsPath)) {
                return "mp4转hls失败,视频文件校验不通过";
            }
            ArrayList<String> list = getTsList(hlsPath);
            if (list == null || list.isEmpty()) {
                return "mp4转hls失败,ts文件为空";
            }
            return "mp4转hls成功";
        } catch (IOException e) {
            e.printStackTrace();
            return "mp4转hls异常: " + e.getMessage();
        }
    }
    private ArrayList<String> getTsList(String hlsPath) {
        ArrayList<String> list = new ArrayList<>();
        BufferedReader read = null;
        try {
            read = new BufferedReader(new FileReader(hlsPath));
            String line;
            String lastLine = "";
            while ((line = read.readLine()) != null) {
                if (line.endsWith(".ts")) {
                    list.add(line);
                }
                lastLine = line;
            }
            if (lastLine.contains("#EXT-X-ENDLIST")) {
                return list;
            }
            return null;
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        } finally {
            if (read != null) {
                try {
                    read.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

3.视频转换业务类:


@Service
public class VideoBiz {//视频转换业务类
    @Value("${hls.ffmpeg‐path}")  //加载application.yml中配置的ffmpeg路径
    String ffmpegPath;
    @Value("${hls.avi-dir}")   //加载视频路径
    String aviDir;
    @Value("${hls.mp4-dir}")
    String mp4Dir;
    @Value("${hls.hls-dir}")
    String hlsDir;
    public String aviToMp4(String aviName) {//avi转mp4
        return AviToMp4Biz.I(ffmpegPath).aviToMp4(aviDir, aviName, mp4Dir);
    }
    public String mp4ToHls(String mp4Name) {//mp4转hls
        return MP4ToHlsBiz.I(ffmpegPath).mp4ToHls(mp4Dir, mp4Name, hlsDir);
    }
}

原文链接:https://blog.csdn.net/a526001650a/article/details/107893093

分类: JavaWindows