说明:
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