这篇文章主要给大家介绍了关于Java使用FFmpeg处理视频文件的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧

前言

本文主要讲述如何使用Java + FFmpeg实现对视频文件的信息提取、码率压缩、分辨率转换等功能;

之前在网上浏览了一大圈Java使用FFmpeg处理音视频的文章,大多都讲的比较简单,楼主在实操过程中踩了很多坑也填了很多坑,希望这份详细的踩坑&填坑指南能帮助到大家;

1. 什么是FFmpeg

点我了解

2. 开发前准备

在使用Java调用FFmpeg处理音视频之前,需要先安装FFmpeg,安装方法分为两种:

  • 引入封装了FFmpeg的开源框架
  • 在系统中手动安装FFmpeg

2.1 引入封装了FFmpeg的开源框架

JAVE.jar(官网点我) 是一个封装了FFmpeg的Java框架,在项目中能直接调用它的API来处理音视频文件;

优点:使用方便,直接在项目中引入JAVE.jar即可处理媒体文件,且开发完成后可以随工程一起打包发布,不需要在目标运行环境内手动安装FFmpeg相关的类库

缺点:JAVE.jar最后一次更新是2009年,其封装的FFmpeg版本是09年或更早前的版本,比较老旧,无法使用一些新特性
(当然也可以看看有没有其他比较新的封装了FFmpeg的框架)

Maven坐标如下:

  1. <dependency>
  2. <groupId>org.ffmpeg</groupId>
  3. <artifactId>sdk</artifactId>
  4. <version>1.0.2</version>
  5. </dependency>

2.2 在系统中手动安装FFmpeg

在运行环境中手动安装FFmpeg稍微有一些麻烦,可以百度 windows/mac安装FFmpeg 这样的关键字,根据网上的安装教程将FFmpeg安装到系统中;

懒人链接:Windows安装教程 Mac安装教程

优点:可以直接调用FFmpeg的相关API处理音视频,FFmpeg版本可控

缺点:手动安装较为麻烦,开发环境与目标运行环境都需要先安装好FFmpeg

3. 使用FFmpeg处理音视频

使用JAVE.jar进行开发与直接使用FFmpeg开发的代码有一些不同,这里以直接使用FFmpeg进行开发的代码进行讲解(开发环境MacOS);(使用JAVE的代码、直接使用FFmpeg的代码都会附在文末供大家下载参考)

通过MediaUtil.java类及其依赖的类,你将可以实现:

  • 解析源视频的基本信息,包括视频格式、时长、码率等;
  • 解析音频、图片的基本信息;
  • 将源视频转换成不同分辨率、不同码率、带或不带音频的新视频;
  • 抽取源视频中指定时间点的帧画面,来生成一张静态图;
  • 抽取源视频中指定时间段的帧画面,来生成一个GIF动态图;
  • 截取源视频中的一段来形成一个新视频;
  • 抽取源视频中的音频信息,生成单独的MP3文件;
  • 对音视频等媒体文件执行自定义的FFmpeg命令;

3.1 代码结构梳理

MediaUtil.java是整个解析程序中的核心类,封装了各种常用的解析方法供外部调用;

MetaInfo.java定义了多媒体数据共有的一些属性,VideoMetaInfo.java MusicMetaInfo.java ImageMetaInfo.java都继承自MetaInfo.java,分别定义了视频、音频、图片数据相关的一些属性;

AnimatedGifEncoder.java LZWEncoder.java NeuQuant.java在抽取视频帧数、制作GIF动态图的时候会使用到;

CrfValueEnum.java 定义了三种常用的FFmpeg压缩视频时使用到的crf值,PresetVauleEnum.java定义了FFmpeg压缩视频时常用的几种压缩速率值;

有关crf、preset的延伸阅读点我

3.2 MediaUtil.java主程序类解析

3.2.1 使用前需要注意的几点

1、指定正确的FFmpeg程序执行路径

MacOS安装好FFmpeg后,可以在控制台中通过which ffmpeg命令获取FFmpeg程序的执行路径,在调用MediaUtil.java前先通过其 setFFmpegPath() 方法设置好FFmpeg程序在系统中的执行路径,然后才能顺利调用到FFmpeg去解析音视频;

Windows系统下该路径理论上应设置为:FFmpeg可执行程序在系统中的绝对路径(实际情况有待大家补充)

2、指定解析音视频信息时需要的正则表达式

因项目需要解析后缀格式为 .MP4 .WMV .AAC 的视频和音频文件,所以我研究了JAVE.jar底层调用FFmpeg时的解析逻辑后,在MediaUtil.java中设置好了匹配这三种格式的正则表达式供解析时使用(参考程序中的 durationRegex videoStreamRegex musicStreamRegex 这三个表达式值);

注意:如果你需要解析其他后缀格式如 .MKV .MP3 这样的媒体文件时,你很可能需要根据实际情况修改durationRegex videoStreamRegex musicStreamRegex 这三个正则表达式的值,否则可能无法解析出正确的信息;

3、程序中的很多默认值你可以根据实际需要修改,比如视频帧抽取的默认宽度或高度值、时长等等;

3.2.2 MediaUtil.java代码

  1. package media;
  2.  
  3. import lombok.extern.slf4j.Slf4j;
  4. import media.domain.ImageMetaInfo;
  5. import media.domain.MusicMetaInfo;
  6. import media.domain.VideoMetaInfo;
  7. import media.domain.gif.AnimatedGifEncoder;
  8. import org.apache.commons.collections4.CollectionUtils;
  9. import org.apache.commons.io.FileUtils;
  10. import org.apache.commons.lang3.StringUtils;
  11.  
  12. import javax.imageio.ImageIO;
  13. import java.awt.image.BufferedImage;
  14. import java.io.*;
  15. import java.sql.Time;
  16. import java.util.ArrayList;
  17. import java.util.Arrays;
  18. import java.util.LinkedList;
  19. import java.util.List;
  20. import java.util.regex.Matcher;
  21. import java.util.regex.Pattern;
  22.  
  23. /**
  24. * 基于FFmpeg内核来编解码音视频信息;
  25. * 使用前需手动在运行环境中安装FFmpeg运行程序,然后正确设置FFmpeg运行路径后MediaUtil.java才能正常调用到FFmpeg程序去处理音视频;
  26. *
  27. * Author: dreamer-1
  28. *
  29. * version: 1.0
  30. *
  31. */
  32. @Slf4j
  33. public class MediaUtil {
  34.  
  35. /**
  36. * 可以处理的视频格式
  37. */
  38. public final static String[] VIDEO_TYPE = { "MP4", "WMV" };
  39. /**
  40. * 可以处理的图片格式
  41. */
  42. public final static String[] IMAGE_TYPE = { "JPG", "JPEG", "PNG", "GIF" };
  43. /**
  44. * 可以处理的音频格式
  45. */
  46. public final static String[] AUDIO_TYPE = { "AAC" };
  47.  
  48. /**
  49. * 视频帧抽取时的默认时间点,第10s(秒)
  50. * (Time类构造参数的单位:ms)
  51. */
  52. private static final Time DEFAULT_TIME = new Time(0, 0, 10);
  53. /**
  54. * 视频帧抽取的默认宽度值,单位:px
  55. */
  56. private static int DEFAULT_WIDTH = 320;
  57. /**
  58. * 视频帧抽取的默认时长,单位:s(秒)
  59. */
  60. private static int DEFAULT_TIME_LENGTH = 10;
  61. /**
  62. * 抽取多张视频帧以合成gif动图时,gif的播放速度
  63. */
  64. private static int DEFAULT_GIF_PLAYTIME = 110;
  65. /**
  66. * FFmpeg程序执行路径
  67. * 当前系统安装好ffmpeg程序并配置好相应的环境变量后,值为ffmpeg可执行程序文件在实际系统中的绝对路径
  68. */
  69. private static String FFMPEG_PATH = "/usr/bin/ffmpeg"; // /usr/bin/ffmpeg
  70.  
  71. /**
  72. * 视频时长正则匹配式
  73. * 用于解析视频及音频的时长等信息时使用;
  74. *
  75. * (.*?)表示:匹配任何除\r\n之外的任何0或多个字符,非贪婪模式
  76. *
  77. */
  78. private static String durationRegex = "Duration: (\\d*?):(\\d*?):(\\d*?)\\.(\\d*?), start: (.*?), bitrate: (\\d*) kb\\/s.*";
  79. private static Pattern durationPattern;
  80. /**
  81. * 视频流信息正则匹配式
  82. * 用于解析视频详细信息时使用;
  83. */
  84. private static String videoStreamRegex = "Stream #\\d:\\d[\\(]??\\S*[\\)]??: Video: (\\S*\\S$?)[^\\,]*, (.*?), (\\d*)x(\\d*)[^\\,]*, (\\d*) kb\\/s, (\\d*[\\.]??\\d*) fps";
  85. private static Pattern videoStreamPattern;
  86. /**
  87. * 音频流信息正则匹配式
  88. * 用于解析音频详细信息时使用;
  89. */
  90. private static String musicStreamRegex = "Stream #\\d:\\d[\\(]??\\S*[\\)]??: Audio: (\\S*\\S$?)(.*), (.*?) Hz, (.*?), (.*?), (\\d*) kb\\/s";;
  91. private static Pattern musicStreamPattern;
  92.  
  93. /**
  94. * 静态初始化时先加载好用于音视频解析的正则匹配式
  95. */
  96. static {
  97. durationPattern = Pattern.compile(durationRegex);
  98. videoStreamPattern = Pattern.compile(videoStreamRegex);
  99. musicStreamPattern = Pattern.compile(musicStreamRegex);
  100. }
  101.  
  102. /**
  103. * 获取当前多媒体处理工具内的ffmpeg的执行路径
  104. * @return
  105. */
  106. public static String getFFmpegPath() {
  107. return FFMPEG_PATH;
  108. }
  109.  
  110. /**
  111. * 设置当前多媒体工具内的ffmpeg的执行路径
  112. * @param ffmpeg_path ffmpeg可执行程序在实际系统中的绝对路径
  113. * @return
  114. */
  115. public static boolean setFFmpegPath(String ffmpeg_path) {
  116. if (StringUtils.isBlank(ffmpeg_path)) {
  117. log.error("--- 设置ffmpeg执行路径失败,因为传入的ffmpeg可执行程序路径为空! ---");
  118. return false;
  119. }
  120. File ffmpegFile = new File(ffmpeg_path);
  121. if (!ffmpegFile.exists()) {
  122. log.error("--- 设置ffmpeg执行路径失败,因为传入的ffmpeg可执行程序路径下的ffmpeg文件不存在! ---");
  123. return false;
  124. }
  125. FFMPEG_PATH = ffmpeg_path;
  126. log.info("--- 设置ffmpeg执行路径成功 --- 当前ffmpeg可执行程序路径为: " + ffmpeg_path);
  127. return true;
  128. }
  129.  
  130. /**
  131. * 测试当前多媒体工具是否可以正常工作
  132. * @return
  133. */
  134. public static boolean isExecutable() {
  135. File ffmpegFile = new File(FFMPEG_PATH);
  136. if (!ffmpegFile.exists()) {
  137. log.error("--- 工作状态异常,因为传入的ffmpeg可执行程序路径下的ffmpeg文件不存在! ---");
  138. return false;
  139. }
  140. List<String> cmds = new ArrayList<>(1);
  141. cmds.add("-version");
  142. String ffmpegVersionStr = executeCommand(cmds);
  143. if (StringUtils.isBlank(ffmpegVersionStr)) {
  144. log.error("--- 工作状态异常,因为ffmpeg命令执行失败! ---");
  145. return false;
  146. }
  147. log.info("--- 工作状态正常 ---");
  148. return true;
  149. }
  150.  
  151. /**
  152. * 执行FFmpeg命令
  153. * @param commonds 要执行的FFmpeg命令
  154. * @return FFmpeg程序在执行命令过程中产生的各信息,执行出错时返回null
  155. */
  156. public static String executeCommand(List<String> commonds) {
  157. if (CollectionUtils.isEmpty(commonds)) {
  158. log.error("--- 指令执行失败,因为要执行的FFmpeg指令为空! ---");
  159. return null;
  160. }
  161. LinkedList<String> ffmpegCmds = new LinkedList<>(commonds);
  162. ffmpegCmds.addFirst(FFMPEG_PATH); // 设置ffmpeg程序所在路径
  163. log.info("--- 待执行的FFmpeg指令为:---" + ffmpegCmds);
  164.  
  165. Runtime runtime = Runtime.getRuntime();
  166. Process ffmpeg = null;
  167. try {
  168. // 执行ffmpeg指令
  169. ProcessBuilder builder = new ProcessBuilder();
  170. builder.command(ffmpegCmds);
  171. ffmpeg = builder.start();
  172. log.info("--- 开始执行FFmpeg指令:--- 执行线程名:" + builder.toString());
  173.  
  174. // 取出输出流和错误流的信息
  175. // 注意:必须要取出ffmpeg在执行命令过程中产生的输出信息,如果不取的话当输出流信息填满jvm存储输出留信息的缓冲区时,线程就回阻塞住
  176. PrintStream errorStream = new PrintStream(ffmpeg.getErrorStream());
  177. PrintStream inputStream = new PrintStream(ffmpeg.getInputStream());
  178. errorStream.start();
  179. inputStream.start();
  180. // 等待ffmpeg命令执行完
  181. ffmpeg.waitFor();
  182.  
  183. // 获取执行结果字符串
  184. String result = errorStream.stringBuffer.append(inputStream.stringBuffer).toString();
  185.  
  186. // 输出执行的命令信息
  187. String cmdStr = Arrays.toString(ffmpegCmds.toArray()).replace(",", "");
  188. String resultStr = StringUtils.isBlank(result) ? "【异常】" : "正常";
  189. log.info("--- 已执行的FFmepg命令: ---" + cmdStr + " 已执行完毕,执行结果: " + resultStr);
  190. return result;
  191.  
  192. } catch (Exception e) {
  193. log.error("--- FFmpeg命令执行出错! --- 出错信息: " + e.getMessage());
  194. return null;
  195.  
  196. } finally {
  197. if (null != ffmpeg) {
  198. ProcessKiller ffmpegKiller = new ProcessKiller(ffmpeg);
  199. // JVM退出时,先通过钩子关闭FFmepg进程
  200. runtime.addShutdownHook(ffmpegKiller);
  201. }
  202. }
  203. }
  204.  
  205. /**
  206. * 视频转换
  207. *
  208. * 注意指定视频分辨率时,宽度和高度必须同时有值;
  209. *
  210. * @param fileInput 源视频路径
  211. * @param fileOutPut 转换后的视频输出路径
  212. * @param withAudio 是否保留音频;true-保留,false-不保留
  213. * @param crf 指定视频的质量系数(值越小,视频质量越高,体积越大;该系数取值为0-51,直接影响视频码率大小),取值参考:CrfValueEnum.code
  214. * @param preset 指定视频的编码速率(速率越快压缩率越低),取值参考:PresetVauleEnum.presetValue
  215. * @param width 视频宽度;为空则保持源视频宽度
  216. * @param height 视频高度;为空则保持源视频高度
  217. */
  218. public static void convertVideo(File fileInput, File fileOutPut, boolean withAudio, Integer crf, String preset, Integer width, Integer height) {
  219. if (null == fileInput || !fileInput.exists()) {
  220. throw new RuntimeException("源视频文件不存在,请检查源视频路径");
  221. }
  222. if (null == fileOutPut) {
  223. throw new RuntimeException("转换后的视频路径为空,请检查转换后的视频存放路径是否正确");
  224. }
  225.  
  226. if (!fileOutPut.exists()) {
  227. try {
  228. fileOutPut.createNewFile();
  229. } catch (IOException e) {
  230. log.error("视频转换时新建输出文件失败");
  231. }
  232. }
  233.  
  234. String format = getFormat(fileInput);
  235. if (!isLegalFormat(format, VIDEO_TYPE)) {
  236. throw new RuntimeException("无法解析的视频格式:" + format);
  237. }
  238.  
  239. List<String> commond = new ArrayList<String>();
  240. commond.add("-i");
  241. commond.add(fileInput.getAbsolutePath());
  242. if (!withAudio) { // 设置是否保留音频
  243. commond.add("-an"); // 去掉音频
  244. }
  245. if (null != width && width > 0 && null != height && height > 0) { // 设置分辨率
  246. commond.add("-s");
  247. String resolution = width.toString() + "x" + height.toString();
  248. commond.add(resolution);
  249. }
  250.  
  251. commond.add("-vcodec"); // 指定输出视频文件时使用的编码器
  252. commond.add("libx264"); // 指定使用x264编码器
  253. commond.add("-preset"); // 当使用x264时需要带上该参数
  254. commond.add(preset); // 指定preset参数
  255. commond.add("-crf"); // 指定输出视频质量
  256. commond.add(crf.toString()); // 视频质量参数,值越小视频质量越高
  257. commond.add("-y"); // 当已存在输出文件时,不提示是否覆盖
  258. commond.add(fileOutPut.getAbsolutePath());
  259.  
  260. executeCommand(commond);
  261. }
  262.  
  263. /**
  264. * 视频帧抽取
  265. * 默认抽取第10秒的帧画面
  266. * 抽取的帧图片默认宽度为300px
  267. *
  268. * 转换后的文件路径以.gif结尾时,默认截取从第10s开始,后10s以内的帧画面来生成gif
  269. *
  270. * @param videoFile 源视频路径
  271. * @param fileOutPut 转换后的文件路径
  272. */
  273. public static void cutVideoFrame(File videoFile, File fileOutPut) {
  274. cutVideoFrame(videoFile, fileOutPut, DEFAULT_TIME);
  275. }
  276.  
  277. /**
  278. * 视频帧抽取(抽取指定时间点的帧画面)
  279. * 抽取的视频帧图片宽度默认为320px
  280. *
  281. * 转换后的文件路径以.gif结尾时,默认截取从指定时间点开始,后10s以内的帧画面来生成gif
  282. *
  283. * @param videoFile 源视频路径
  284. * @param fileOutPut 转换后的文件路径
  285. * @param time 指定抽取视频帧的时间点(单位:s)
  286. */
  287. public static void cutVideoFrame(File videoFile, File fileOutPut, Time time) {
  288. cutVideoFrame(videoFile, fileOutPut, time, DEFAULT_WIDTH);
  289. }
  290.  
  291. /**
  292. * 视频帧抽取(抽取指定时间点、指定宽度值的帧画面)
  293. * 只需指定视频帧的宽度,高度随宽度自动计算
  294. *
  295. * 转换后的文件路径以.gif结尾时,默认截取从指定时间点开始,后10s以内的帧画面来生成gif
  296. *
  297. * @param videoFile 源视频路径
  298. * @param fileOutPut 转换后的文件路径
  299. * @param time 指定要抽取第几秒的视频帧(单位:s)
  300. * @param width 抽取的视频帧图片的宽度(单位:px)
  301. */
  302. public static void cutVideoFrame(File videoFile, File fileOutPut, Time time, int width) {
  303. if (null == videoFile || !videoFile.exists()) {
  304. throw new RuntimeException("源视频文件不存在,请检查源视频路径");
  305. }
  306. if (null == fileOutPut) {
  307. throw new RuntimeException("转换后的视频路径为空,请检查转换后的视频存放路径是否正确");
  308. }
  309. VideoMetaInfo info = getVideoMetaInfo(videoFile);
  310. if (null == info) {
  311. log.error("--- 未能解析源视频信息,视频帧抽取操作失败 --- 源视频: " + videoFile);
  312. return;
  313. }
  314. int height = width * info.getHeight() / info.getWidth(); // 根据宽度计算适合的高度,防止画面变形
  315. cutVideoFrame(videoFile, fileOutPut, time, width, height);
  316. }
  317.  
  318. /**
  319. * 视频帧抽取(抽取指定时间点、指定宽度值、指定高度值的帧画面)
  320. *
  321. * 转换后的文件路径以.gif结尾时,默认截取从指定时间点开始,后10s以内的帧画面来生成gif
  322. *
  323. * @param videoFile 源视频路径
  324. * @param fileOutPut 转换后的文件路径
  325. * @param time 指定要抽取第几秒的视频帧(单位:s)
  326. * @param width 抽取的视频帧图片的宽度(单位:px)
  327. * @param height 抽取的视频帧图片的高度(单位:px)
  328. */
  329. public static void cutVideoFrame(File videoFile, File fileOutPut, Time time, int width, int height) {
  330. if (null == videoFile || !videoFile.exists()) {
  331. throw new RuntimeException("源视频文件不存在,请检查源视频路径");
  332. }
  333. if (null == fileOutPut) {
  334. throw new RuntimeException("转换后的视频路径为空,请检查转换后的视频存放路径是否正确");
  335. }
  336. String format = getFormat(fileOutPut);
  337. if (!isLegalFormat(format, IMAGE_TYPE)) {
  338. throw new RuntimeException("无法生成指定格式的帧图片:" + format);
  339. }
  340. String fileOutPutPath = fileOutPut.getAbsolutePath();
  341. if (!"GIF".equals(StringUtils.upperCase(format))) {
  342. // 输出路径不是以.gif结尾,抽取并生成一张静态图
  343. cutVideoFrame(videoFile, fileOutPutPath, time, width, height, 1, false);
  344. } else {
  345. // 抽取并生成一个gif(gif由10张静态图构成)
  346. String path = fileOutPut.getParent();
  347. String name = fileOutPut.getName();
  348. // 创建临时文件存储多张静态图用于生成gif
  349. String tempPath = path + File.separator + System.currentTimeMillis() + "_" + name.substring(0, name.indexOf("."));
  350. File file = new File(tempPath);
  351. if (!file.exists()) {
  352. file.mkdir();
  353. }
  354. try {
  355. cutVideoFrame(videoFile, tempPath, time, width, height, DEFAULT_TIME_LENGTH, true);
  356. // 生成gif
  357. String images[] = file.list();
  358. for (int i = 0; i < images.length; i++) {
  359. images[i] = tempPath + File.separator + images[i];
  360. }
  361. createGifImage(images, fileOutPut.getAbsolutePath(), DEFAULT_GIF_PLAYTIME);
  362. } catch (Exception e) {
  363. log.error("--- 截取视频帧操作出错 --- 错误信息:" + e.getMessage());
  364. } finally {
  365. // 删除用于生成gif的临时文件
  366. String images[] = file.list();
  367. for (int i = 0; i < images.length; i++) {
  368. File fileDelete = new File(tempPath + File.separator + images[i]);
  369. fileDelete.delete();
  370. }
  371. file.delete();
  372. }
  373. }
  374. }
  375.  
  376. /**
  377. * 视频帧抽取(抽取指定时间点、指定宽度值、指定高度值、指定时长、指定单张/多张的帧画面)
  378. *
  379. * @param videoFile 源视频
  380. * @param path 转换后的文件输出路径
  381. * @param time 开始截取视频帧的时间点(单位:s)
  382. * @param width 截取的视频帧图片的宽度(单位:px)
  383. * @param height 截取的视频帧图片的高度(单位:px,需要大于20)
  384. * @param timeLength 截取的视频帧的时长(从time开始算,单位:s,需小于源视频的最大时长)
  385. * @param isContinuty false - 静态图(只截取time时间点的那一帧图片),true - 动态图(截取从time时间点开始,timelength这段时间内的多张帧图)
  386. */
  387. private static void cutVideoFrame(File videoFile, String path, Time time, int width, int height, int timeLength, boolean isContinuty) {
  388. if (videoFile == null || !videoFile.exists()) {
  389. throw new RuntimeException("源视频文件不存在,源视频路径: ");
  390. }
  391. if (null == path) {
  392. throw new RuntimeException("转换后的文件路径为空,请检查转换后的文件存放路径是否正确");
  393. }
  394. VideoMetaInfo info = getVideoMetaInfo(videoFile);
  395. if (null == info) {
  396. throw new RuntimeException("未解析到视频信息");
  397. }
  398. if (time.getTime() + timeLength > info.getDuration()) {
  399. throw new RuntimeException("开始截取视频帧的时间点不合法:" + time.toString() + ",因为截取时间点晚于视频的最后时间点");
  400. }
  401. if (width <= 20 || height <= 20) {
  402. throw new RuntimeException("截取的视频帧图片的宽度或高度不合法,宽高值必须大于20");
  403. }
  404. try {
  405. List<String> commond = new ArrayList<String>();
  406. commond.add("-ss");
  407. commond.add(time.toString());
  408. if (isContinuty) {
  409. commond.add("-t");
  410. commond.add(timeLength + "");
  411. } else {
  412. commond.add("-vframes");
  413. commond.add("1");
  414. }
  415. commond.add("-i");
  416. commond.add(videoFile.getAbsolutePath());
  417. commond.add("-an");
  418. commond.add("-f");
  419. commond.add("image2");
  420. if (isContinuty) {
  421. commond.add("-r");
  422. commond.add("3");
  423. }
  424. commond.add("-s");
  425. commond.add(width + "*" + height);
  426. if (isContinuty) {
  427. commond.add(path + File.separator + "foo-%03d.jpeg");
  428. } else {
  429. commond.add(path);
  430. }
  431.  
  432. executeCommand(commond);
  433. } catch (Exception e) {
  434. log.error("--- 视频帧抽取过程出错 --- 错误信息: " + e.getMessage());
  435. }
  436. }
  437.  
  438. /**
  439. * 截取视频中的某一段,生成新视频
  440. *
  441. * @param videoFile 源视频路径
  442. * @param outputFile 转换后的视频路径
  443. * @param startTime 开始抽取的时间点(单位:s)
  444. * @param timeLength 需要抽取的时间段(单位:s,需小于源视频最大时长);例如:该参数值为10时即抽取从startTime开始之后10秒内的视频作为新视频
  445. */
  446. public static void cutVideo(File videoFile, File outputFile, Time startTime, int timeLength) {
  447. if (videoFile == null || !videoFile.exists()) {
  448. throw new RuntimeException("视频文件不存在:");
  449. }
  450. if (null == outputFile) {
  451. throw new RuntimeException("转换后的视频路径为空,请检查转换后的视频存放路径是否正确");
  452. }
  453. VideoMetaInfo info = getVideoMetaInfo(videoFile);
  454. if (null == info) {
  455. throw new RuntimeException("未解析到视频信息");
  456. }
  457. if (startTime.getTime() + timeLength > info.getDuration()) {
  458. throw new RuntimeException("截取时间不合法:" + startTime.toString() + ",因为截取时间大于视频的时长");
  459. }
  460. try {
  461. if (!outputFile.exists()) {
  462. outputFile.createNewFile();
  463. }
  464. List<String> commond = new ArrayList<String>();
  465. commond.add("-ss");
  466. commond.add(startTime.toString());
  467. commond.add("-t");
  468. commond.add("" + timeLength);
  469. commond.add("-i");
  470. commond.add(videoFile.getAbsolutePath());
  471. commond.add("-vcodec");
  472. commond.add("copy");
  473. commond.add("-acodec");
  474. commond.add("copy");
  475. commond.add(outputFile.getAbsolutePath());
  476. executeCommand(commond);
  477. } catch (IOException e) {
  478. log.error("--- 视频截取过程出错 ---");
  479. }
  480. }
  481.  
  482. /**
  483. * 抽取视频里的音频信息
  484. * 只能抽取成MP3文件
  485. * @param videoFile 源视频文件
  486. * @param audioFile 从源视频提取的音频文件
  487. */
  488. public static void getAudioFromVideo(File videoFile, File audioFile) {
  489. if (null == videoFile || !videoFile.exists()) {
  490. throw new RuntimeException("源视频文件不存在: ");
  491. }
  492. if (null == audioFile) {
  493. throw new RuntimeException("要提取的音频路径为空:");
  494. }
  495. String format = getFormat(audioFile);
  496. if (!isLegalFormat(format, AUDIO_TYPE)) {
  497. throw new RuntimeException("无法生成指定格式的音频:" + format + " 请检查要输出的音频文件是否是AAC类型");
  498. }
  499. try {
  500. if (!audioFile.exists()) {
  501. audioFile.createNewFile();
  502. }
  503.  
  504. List<String> commond = new ArrayList<String>();
  505. commond.add("-i");
  506. commond.add(videoFile.getAbsolutePath());
  507. commond.add("-vn"); // no video,去除视频信息
  508. commond.add("-y");
  509. commond.add("-acodec");
  510. commond.add("copy");
  511. commond.add(audioFile.getAbsolutePath());
  512. executeCommand(commond);
  513. } catch (Exception e) {
  514. log.error("--- 抽取视频中的音频信息的过程出错 --- 错误信息: " + e.getMessage());
  515. }
  516. }
  517.  
  518. /**
  519. * 解析视频的基本信息(从文件中)
  520. *
  521. * 解析出的视频信息一般为以下格式:
  522. * Input #0, mov,mp4,m4a,3gp,3g2,mj2, from '6.mp4':
  523. * Duration: 00:00:30.04, start: 0.000000, bitrate: 19031 kb/s
  524. * Stream #0:0(eng): Video: h264 (Main) (avc1 / 0x31637661), yuv420p(tv, bt709), 1920x1080, 18684 kb/s, 25 fps, 25 tbr, 25k tbn, 50 tbc (default)
  525. * Stream #0:1(eng): Audio: aac (LC) (mp4a / 0x6134706D), 48000 Hz, stereo, fltp, 317 kb/s (default)
  526. *
  527. * 注解:
  528. * Duration: 00:00:30.04【视频时长】, start: 0.000000【视频开始时间】, bitrate: 19031 kb/s【视频比特率/码率】
  529. * Stream #0:0(eng): Video: h264【视频编码格式】 (Main) (avc1 / 0x31637661), yuv420p(tv, bt709), 1920x1080【视频分辨率,宽x高】, 18684【视频比特率】 kb/s, 25【视频帧率】 fps, 25 tbr, 25k tbn, 50 tbc (default)
  530. * Stream #0:1(eng): Audio: aac【音频格式】 (LC) (mp4a / 0x6134706D), 48000【音频采样率】 Hz, stereo, fltp, 317【音频码率】 kb/s (default)
  531. *
  532. * @param videoFile 源视频路径
  533. * @return 视频的基本信息,解码失败时返回null
  534. */
  535. public static VideoMetaInfo getVideoMetaInfo(File videoFile) {
  536. if (null == videoFile || !videoFile.exists()) {
  537. log.error("--- 解析视频信息失败,因为要解析的源视频文件不存在 ---");
  538. return null;
  539. }
  540.  
  541. VideoMetaInfo videoInfo = new VideoMetaInfo();
  542.  
  543. String parseResult = getMetaInfoFromFFmpeg(videoFile);
  544.  
  545. Matcher durationMacher = durationPattern.matcher(parseResult);
  546. Matcher videoStreamMacher = videoStreamPattern.matcher(parseResult);
  547. Matcher videoMusicStreamMacher = musicStreamPattern.matcher(parseResult);
  548.  
  549. Long duration = 0L; // 视频时长
  550. Integer videoBitrate = 0; // 视频码率
  551. String videoFormat = getFormat(videoFile); // 视频格式
  552. Long videoSize = videoFile.length(); // 视频大小
  553.  
  554. String videoEncoder = ""; // 视频编码器
  555. Integer videoHeight = 0; // 视频高度
  556. Integer videoWidth = 0; // 视频宽度
  557. Float videoFramerate = 0F; // 视频帧率
  558.  
  559. String musicFormat = ""; // 音频格式
  560. Long samplerate = 0L; // 音频采样率
  561. Integer musicBitrate = 0; // 音频码率
  562.  
  563. try {
  564. // 匹配视频播放时长等信息
  565. if (durationMacher.find()) {
  566. long hours = (long)Integer.parseInt(durationMacher.group(1));
  567. long minutes = (long)Integer.parseInt(durationMacher.group(2));
  568. long seconds = (long)Integer.parseInt(durationMacher.group(3));
  569. long dec = (long)Integer.parseInt(durationMacher.group(4));
  570. duration = dec * 100L + seconds * 1000L + minutes * 60L * 1000L + hours * 60L * 60L * 1000L;
  571. //String startTime = durationMacher.group(5) + "ms";
  572. videoBitrate = Integer.parseInt(durationMacher.group(6));
  573. }
  574. // 匹配视频分辨率等信息
  575. if (videoStreamMacher.find()) {
  576. videoEncoder = videoStreamMacher.group(1);
  577. String s2 = videoStreamMacher.group(2);
  578. videoWidth = Integer.parseInt(videoStreamMacher.group(3));
  579. videoHeight = Integer.parseInt(videoStreamMacher.group(4));
  580. String s5 = videoStreamMacher.group(5);
  581. videoFramerate = Float.parseFloat(videoStreamMacher.group(6));
  582. }
  583. // 匹配视频中的音频信息
  584. if (videoMusicStreamMacher.find()) {
  585. musicFormat = videoMusicStreamMacher.group(1); // 提取音频格式
  586. //String s2 = videoMusicStreamMacher.group(2);
  587. samplerate = Long.parseLong(videoMusicStreamMacher.group(3)); // 提取采样率
  588. //String s4 = videoMusicStreamMacher.group(4);
  589. //String s5 = videoMusicStreamMacher.group(5);
  590. musicBitrate = Integer.parseInt(videoMusicStreamMacher.group(6)); // 提取比特率
  591. }
  592. } catch (Exception e) {
  593. log.error("--- 解析视频参数信息出错! --- 错误信息: " + e.getMessage());
  594. return null;
  595. }
  596.  
  597. // 封装视频中的音频信息
  598. MusicMetaInfo musicMetaInfo = new MusicMetaInfo();
  599. musicMetaInfo.setFormat(musicFormat);
  600. musicMetaInfo.setDuration(duration);
  601. musicMetaInfo.setBitRate(musicBitrate);
  602. musicMetaInfo.setSampleRate(samplerate);
  603. // 封装视频信息
  604. VideoMetaInfo videoMetaInfo = new VideoMetaInfo();
  605. videoMetaInfo.setFormat(videoFormat);
  606. videoMetaInfo.setSize(videoSize);
  607. videoMetaInfo.setBitRate(videoBitrate);
  608. videoMetaInfo.setDuration(duration);
  609. videoMetaInfo.setEncoder(videoEncoder);
  610. videoMetaInfo.setFrameRate(videoFramerate);
  611. videoMetaInfo.setHeight(videoHeight);
  612. videoMetaInfo.setWidth(videoWidth);
  613. videoMetaInfo.setMusicMetaInfo(musicMetaInfo);
  614.  
  615. return videoMetaInfo;
  616. }
  617.  
  618. /**
  619. * 获取视频的基本信息(从流中)
  620. *
  621. * @param inputStream 源视频流路径
  622. * @return 视频的基本信息,解码失败时返回null
  623. */
  624. public static VideoMetaInfo getVideoMetaInfo(InputStream inputStream) {
  625. VideoMetaInfo videoInfo = new VideoMetaInfo();
  626. try {
  627. File file = File.createTempFile("tmp", null);
  628. if (!file.exists()) {
  629. return null;
  630. }
  631. FileUtils.copyInputStreamToFile(inputStream, file);
  632. videoInfo = getVideoMetaInfo(file);
  633. file.deleteOnExit();
  634. return videoInfo;
  635. } catch (Exception e) {
  636. log.error("--- 从流中获取视频基本信息出错 --- 错误信息: " + e.getMessage());
  637. return null;
  638. }
  639. }
  640.  
  641. /**
  642. * 获取音频的基本信息(从文件中)
  643. * @param musicFile 音频文件路径
  644. * @return 音频的基本信息,解码失败时返回null
  645. */
  646. public static MusicMetaInfo getMusicMetaInfo(File musicFile) {
  647. if (null == musicFile || !musicFile.exists()) {
  648. log.error("--- 无法获取音频信息,因为要解析的音频文件为空 ---");
  649. return null;
  650. }
  651. // 获取音频信息字符串,方便后续解析
  652. String parseResult = getMetaInfoFromFFmpeg(musicFile);
  653.  
  654. Long duration = 0L; // 音频时长
  655. Integer musicBitrate = 0; // 音频码率
  656. Long samplerate = 0L; // 音频采样率
  657. String musicFormat = ""; // 音频格式
  658. Long musicSize = musicFile.length(); // 音频大小
  659.  
  660. Matcher durationMacher = durationPattern.matcher(parseResult);
  661. Matcher musicStreamMacher = musicStreamPattern.matcher(parseResult);
  662.  
  663. try {
  664. // 匹配音频播放时长等信息
  665. if (durationMacher.find()) {
  666. long hours = (long)Integer.parseInt(durationMacher.group(1));
  667. long minutes = (long)Integer.parseInt(durationMacher.group(2));
  668. long seconds = (long)Integer.parseInt(durationMacher.group(3));
  669. long dec = (long)Integer.parseInt(durationMacher.group(4));
  670. duration = dec * 100L + seconds * 1000L + minutes * 60L * 1000L + hours * 60L * 60L * 1000L;
  671. //String startTime = durationMacher.group(5) + "ms";
  672. musicBitrate = Integer.parseInt(durationMacher.group(6));
  673. }
  674. // 匹配音频采样率等信息
  675. if (musicStreamMacher.find()) {
  676. musicFormat = musicStreamMacher.group(1); // 提取音频格式
  677. //String s2 = videoMusicStreamMacher.group(2);
  678. samplerate = Long.parseLong(musicStreamMacher.group(3)); // 提取采样率
  679. //String s4 = videoMusicStreamMacher.group(4);
  680. //String s5 = videoMusicStreamMacher.group(5);
  681. musicBitrate = Integer.parseInt(musicStreamMacher.group(6)); // 提取比特率
  682. }
  683. } catch (Exception e) {
  684. log.error("--- 解析音频参数信息出错! --- 错误信息: " + e.getMessage());
  685. return null;
  686. }
  687.  
  688. // 封装视频中的音频信息
  689. MusicMetaInfo musicMetaInfo = new MusicMetaInfo();
  690. musicMetaInfo.setFormat(musicFormat);
  691. musicMetaInfo.setDuration(duration);
  692. musicMetaInfo.setBitRate(musicBitrate);
  693. musicMetaInfo.setSampleRate(samplerate);
  694. musicMetaInfo.setSize(musicSize);
  695. return musicMetaInfo;
  696. }
  697.  
  698. /**
  699. * 获取音频的基本信息(从流中)
  700. * @param inputStream 源音乐流路径
  701. * @return 音频基本信息,解码出错时返回null
  702. */
  703. public static MusicMetaInfo getMusicMetaInfo(InputStream inputStream) {
  704. MusicMetaInfo musicMetaInfo = new MusicMetaInfo();
  705. try {
  706. File file = File.createTempFile("tmp", null);
  707. if (!file.exists()) {
  708. return null;
  709. }
  710. FileUtils.copyInputStreamToFile(inputStream, file);
  711. musicMetaInfo = getMusicMetaInfo(file);
  712. file.deleteOnExit();
  713. return musicMetaInfo;
  714. } catch (Exception e) {
  715. log.error("--- 从流中获取音频基本信息出错 --- 错误信息: " + e.getMessage());
  716. return null;
  717. }
  718. }
  719.  
  720. /**
  721. * 获取图片的基本信息(从流中)
  722. *
  723. * @param inputStream 源图片路径
  724. * @return 图片的基本信息,获取信息失败时返回null
  725. */
  726. public static ImageMetaInfo getImageInfo(InputStream inputStream) {
  727. BufferedImage image = null;
  728. ImageMetaInfo imageInfo = new ImageMetaInfo();
  729. try {
  730. image = ImageIO.read(inputStream);
  731. imageInfo.setWidth(image.getWidth());
  732. imageInfo.setHeight(image.getHeight());
  733. imageInfo.setSize(Long.valueOf(String.valueOf(inputStream.available())));
  734. return imageInfo;
  735. } catch (Exception e) {
  736. log.error("--- 获取图片的基本信息失败 --- 错误信息: " + e.getMessage());
  737. return null;
  738. }
  739. }
  740.  
  741. /**
  742. * 获取图片的基本信息 (从文件中)
  743. *
  744. * @param imageFile 源图片路径
  745. * @return 图片的基本信息,获取信息失败时返回null
  746. */
  747. public static ImageMetaInfo getImageInfo(File imageFile) {
  748. BufferedImage image = null;
  749. ImageMetaInfo imageInfo = new ImageMetaInfo();
  750. try {
  751. if (null == imageFile || !imageFile.exists()) {
  752. return null;
  753. }
  754. image = ImageIO.read(imageFile);
  755. imageInfo.setWidth(image.getWidth());
  756. imageInfo.setHeight(image.getHeight());
  757. imageInfo.setSize(imageFile.length());
  758. imageInfo.setFormat(getFormat(imageFile));
  759. return imageInfo;
  760. } catch (Exception e) {
  761. log.error("--- 获取图片的基本信息失败 --- 错误信息: " + e.getMessage());
  762. return null;
  763. }
  764. }
  765.  
  766. /**
  767. * 检查文件类型是否是给定的类型
  768. * @param inputFile 源文件
  769. * @param givenFormat 指定的文件类型;例如:{"MP4", "AVI"}
  770. * @return
  771. */
  772. public static boolean isGivenFormat(File inputFile, String[] givenFormat) {
  773. if (null == inputFile || !inputFile.exists()) {
  774. log.error("--- 无法检查文件类型是否满足要求,因为要检查的文件不存在 --- 源文件: " + inputFile);
  775. return false;
  776. }
  777. if (null == givenFormat || givenFormat.length <= 0) {
  778. log.error("--- 无法检查文件类型是否满足要求,因为没有指定的文件类型 ---");
  779. return false;
  780. }
  781. String fomat = getFormat(inputFile);
  782. return isLegalFormat(fomat, givenFormat);
  783. }
  784.  
  785. /**
  786. * 使用FFmpeg的"-i"命令来解析视频信息
  787. * @param inputFile 源媒体文件
  788. * @return 解析后的结果字符串,解析失败时为空
  789. */
  790. public static String getMetaInfoFromFFmpeg(File inputFile) {
  791. if (inputFile == null || !inputFile.exists()) {
  792. throw new RuntimeException("源媒体文件不存在,源媒体文件路径: ");
  793. }
  794. List<String> commond = new ArrayList<String>();
  795. commond.add("-i");
  796. commond.add(inputFile.getAbsolutePath());
  797. String executeResult = MediaUtil.executeCommand(commond);
  798. return executeResult;
  799. }
  800.  
  801. /**
  802. * 检测视频格式是否合法
  803. * @param format
  804. * @param formats
  805. * @return
  806. */
  807. private static boolean isLegalFormat(String format, String formats[]) {
  808. for (String item : formats) {
  809. if (item.equals(StringUtils.upperCase(format))) {
  810. return true;
  811. }
  812. }
  813. return false;
  814. }
  815.  
  816. /**
  817. * 创建gif
  818. *
  819. * @param image 多个jpg文件名(包含路径)
  820. * @param outputPath 生成的gif文件名(包含路径)
  821. * @param playTime 播放的延迟时间,可调整gif的播放速度
  822. */
  823. private static void createGifImage(String image[], String outputPath, int playTime) {
  824. if (null == outputPath) {
  825. throw new RuntimeException("转换后的GIF路径为空,请检查转换后的GIF存放路径是否正确");
  826. }
  827. try {
  828. AnimatedGifEncoder encoder = new AnimatedGifEncoder();
  829. encoder.setRepeat(0);
  830. encoder.start(outputPath);
  831. BufferedImage src[] = new BufferedImage[image.length];
  832. for (int i = 0; i < src.length; i++) {
  833. encoder.setDelay(playTime); // 设置播放的延迟时间
  834. src[i] = ImageIO.read(new File(image[i])); // 读入需要播放的jpg文件
  835. encoder.addFrame(src[i]); // 添加到帧中
  836. }
  837. encoder.finish();
  838. } catch (Exception e) {
  839. log.error("--- 多张静态图转换成动态GIF图的过程出错 --- 错误信息: " + e.getMessage());
  840. }
  841. }
  842.  
  843. /**
  844. * 获取指定文件的后缀名
  845. * @param file
  846. * @return
  847. */
  848. private static String getFormat(File file) {
  849. String fileName = file.getName();
  850. String format = fileName.substring(fileName.indexOf(".") + 1);
  851. return format;
  852. }
  853.  
  854. /**
  855. * 在程序退出前结束已有的FFmpeg进程
  856. */
  857. private static class ProcessKiller extends Thread {
  858. private Process process;
  859.  
  860. public ProcessKiller(Process process) {
  861. this.process = process;
  862. }
  863.  
  864. @Override
  865. public void run() {
  866. this.process.destroy();
  867. log.info("--- 已销毁FFmpeg进程 --- 进程名: " + process.toString());
  868. }
  869. }
  870.  
  871. /**
  872. * 用于取出ffmpeg线程执行过程中产生的各种输出和错误流的信息
  873. */
  874. static class PrintStream extends Thread {
  875. InputStream inputStream = null;
  876. BufferedReader bufferedReader = null;
  877. StringBuffer stringBuffer = new StringBuffer();
  878.  
  879. public PrintStream(InputStream inputStream) {
  880. this.inputStream = inputStream;
  881. }
  882.  
  883. @Override
  884. public void run() {
  885. try {
  886. if (null == inputStream) {
  887. log.error("--- 读取输出流出错!因为当前输出流为空!---");
  888. }
  889. bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
  890. String line = null;
  891. while ((line = bufferedReader.readLine()) != null) {
  892. log.info(line);
  893. stringBuffer.append(line);
  894. }
  895. } catch (Exception e) {
  896. log.error("--- 读取输入流出错了!--- 错误信息:" + e.getMessage());
  897. } finally {
  898. try {
  899. if (null != bufferedReader) {
  900. bufferedReader.close();
  901. }
  902. if (null != inputStream) {
  903. inputStream.close();
  904. }
  905. } catch (IOException e) {
  906. log.error("--- 调用PrintStream读取输出流后,关闭流时出错!---");
  907. }
  908. }
  909. }
  910. }
  911.  
  912. }

3.2.3 踩坑&填坑

1、在Linux等服务器上部署Java程序进行视频压缩时,多注意一下运行账号的权限问题,有时候可能是由于运行程序没有足够的文件操作权限,导致压缩过程失败;

2、第一版程序上线后,偶尔会出现这样的问题:

调用MediaUtil.java进行视频压缩过程中,整个程序突然“卡住”,后台也没有日志再打印出来,此时整个压缩过程还没有完成,像是线程突然阻塞住了;

经过多番查找,发现Java调用FFmpeg时,实际是在JVM里产生一个子进程来执行压缩过程,这个子进程与JVM建立三个通道链接(包括标准输入、标准输出、标准错误流),在压缩过程中,实际会不停地向标准输出和错误流中写入信息;

因为本地系统对标准输出及错误流提供的缓冲区大小有限,当写入标准输出和错误流的信息填满缓冲区时,执行压缩的进程就会阻塞住;

所以在压缩过程中,需要单独创建两个线程不停读取标准输出及错误流中的信息,防止整个压缩进程阻塞;(参考MediaUtil.java中的 executeCommand() 方法中的 errorStream 和 inputStream 这两个内部类实例的操作)

3.3 在CentOS服务器安装FFmpeg指南

因项目最后部署在CentOS服务器上,需提前在服务器上安装好FFmpeg程序,这过程中也踩了不少坑,针对此写了另一篇总结文章,参考这里 点我哦

4. 源码下载

这里提供两种版本的源码供大家下载参考:

  • 引入封装了FFmpeg的开源框架Jave.jar的版本 点我下载
  • 在系统中手动安装FFmpeg的版本 点我下载

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对脚本之家的支持。

Java使用FFmpeg处理视频文件的方法教程的更多相关文章

  1. Java使用FFmpeg处理视频文件指南

    Java使用FFmpeg处理视频文件指南 本文主要讲述如何使用Java + FFmpeg实现对视频文件的信息提取.码率压缩.分辨率转换等功能: 之前在网上浏览了一大圈Java使用FFmpeg处理音视频 ...

  2. java调用ffmpeg获取视频文件信息的一些参数

    一.下载ffmpeg http://www.ffmpeg.org/download.html 主要需要bin目录下的ffmpeg可执行文件 二.java代码实现 package com.aw.util ...

  3. 使用ffmpeg合并视频文件的三种方法

    ffmpeg合并视频的方法有三种.国内大多数仅介绍了其中之一.于是觉得有必要翻译一下.其实在ffmpeg的 FAQ文档中有比较详细的说明. 使用concat协议进行视频文件的合并 这种方式的适用场景是 ...

  4. Java调用FFmpeg进行视频处理及Builder设计模式的应用

    1.FFmpeg是什么 FFmpeg(https://www.ffmpeg.org)是一套可以用来记录.转换数字音频.视频,并能将其转化为流的开源计算机程序.它用来干吗呢?视频采集.视频格式转化.视频 ...

  5. Java+Windows+ffmpeg实现视频转换

    最近由于项目需要,研究了一下如何用Java实现视频转换,“着实”废了点心思,整理整理,写出给自己备忘下. 思路 由于之前没有没法过相关功能的经验,一开始来真不知道从哪里入手.当然,这个解决,googl ...

  6. 实战FFmpeg--iOS平台使用FFmpeg将视频文件转换为YUV文件

    做播放器的开发这里面涉及的东西太多,我只能一步步往前走,慢慢深入.播放器播放视频采用的是渲染yuv文件.首先,要知道yuv文件是怎么转换得来的,其次,要知道怎么把视频文件保存为yuv文件.雷神的文章1 ...

  7. ffmpeg修改视频文件的分辨率

    在本文中,我们将展示如何调整任何视频文件的大小. 这种方法是在Linux系统(几乎任何发行版)中调整视频文件大小的最佳方法之一,也是Windows和Mac用户的绝佳替代方案. 更改视频文件的分辨率将是 ...

  8. 【java】获取解析资源文件的方法

    关于资源文件的读取,有很多种方法,下面补充了多种方法 1.java.util.ResourceBundle 使用java自带的util包下的ResourceBundle类获取,使用方法最简单 //获取 ...

  9. 使用FFmpeg处理视频文件:视频转码、剪切、合并、播放速调整

    安装 略. 转码 最简单命令如下: ffmpeg -i out.ogv -vcodec h264 out.mp4ffmpeg -i out.ogv -vcodec mpeg4 out.mp4ffmpe ...

随机推荐

  1. 通过崩溃trace来查找问题原因

    从友盟中, 我们可能会得到如下信息: Application received signal SIGSEGV (null) ( 0 CoreFoundation 0x359348a7 __except ...

  2. 用ant编译打包时 警告:编码 GBK 的不可映射字符

    原因,参考http://zhidao.baidu.com/question/26901568.html 添加如下的红色一行后编译通过<target name="compile" ...

  3. C++之封装

    希望暴露public 希望隐藏private 对象实例化有两种方式,从栈实例化,从堆(new出来的)实例化. 以谁做什么作为核心. public 放前面,private放后面(属性可以定义为priva ...

  4. 一步完成MySQL向Redis迁移

    在把一个大表从 MySQL 迁移到 Redis 时,你可能会发现,每次提取.转换.导入一条数据是让人难以忍受的慢!这里有一个技巧,你可以通过使用管道把 MySQL 的输出直接输入到 redis-cli ...

  5. Eclipse_插件_02_jd-eclipse插件的安装

    1.去官网下载 jd-eclipse插件 2.解压后的文件夹A放到eclipse的drops文件夹下 3.删掉多余文件,确保文件夹A下只有plugin 和 freature 两个文件夹 4.清空osg ...

  6. (转)HLS协议,html5视频直播一站式扫盲

    本文来自于腾讯bugly开发者社区,原文地址:http://bugly.qq.com/bbs/forum.php?mod=viewthread&tid=1277 视频直播这么火,再不学就 ou ...

  7. Maven-将jar包安装到本地仓库

    因为项目需要,使用的是sqlserver数据库,但是却找不到其对应的pom依赖,所以需要将本地jar包安装到本地仓库,定义pom依赖.以此为例,其他jar包均可参考该方式 cmd命令语句: mvn i ...

  8. linux命令学习笔记(44):top命令

    top命令是Linux下常用的性能分析工具,能够实时显示系统中各个进程的资源占用状况,类似于Windows的任务管 理器.下面详细介绍它的使用方法.top是一个动态显示过程,即可以通过用户按键来不断刷 ...

  9. 网络编程学习笔记-linux常用的网络命令

    网络参数设置命令 所有时刻如果你想要做好自己的网络参数设置,包括IP参数.路由参数和无线网络等,就得要了解下面这些相关的命令才行.其中Route及ip这两条命令是比较重要的.当然,比较早期的用法,我们 ...

  10. 关于CDH

    进入到任何一个Host的页面,点击“components",就可以看到这个主机安装的组件的版本