libilibi项目总结(18)FFmpeg 的使用

devtools/2024/12/23 22:44:47/

FFmpeg工具类

java">import com.easylive.entity.config.AppConfig;
import com.easylive.entity.constants.Constants;
import org.springframework.stereotype.Component;import javax.annotation.Resource;
import java.io.File;
import java.math.BigDecimal;@Component
public class FFmpegUtils {@Resourceprivate AppConfig appConfig;/*** 生成图片缩略图** @param filePath* @return*/public void createImageThumbnail(String filePath) {final String CMD_CREATE_IMAGE_THUMBNAIL = "ffmpeg -i \"%s\" -vf scale=200:-1 \"%s\"";String cmd = String.format(CMD_CREATE_IMAGE_THUMBNAIL, filePath, filePath + Constants.IMAGE_THUMBNAIL_SUFFIX);ProcessUtils.executeCommand(cmd, appConfig.getShowFFmpegLog());}/*** 获取视频编码** @param videoFilePath* @return*/public String getVideoCodec(String videoFilePath) {final String CMD_GET_CODE = "ffprobe -v error -select_streams v:0 -show_entries stream=codec_name \"%s\"";String cmd = String.format(CMD_GET_CODE, videoFilePath);String result = ProcessUtils.executeCommand(cmd, appConfig.getShowFFmpegLog());result = result.replace("\n", "");result = result.substring(result.indexOf("=") + 1);String codec = result.substring(0, result.indexOf("["));return codec;}public void convertHevc2Mp4(String newFileName, String videoFilePath) {String CMD_HEVC_264 = "ffmpeg -i %s -c:v libx264 -crf 20 %s";String cmd = String.format(CMD_HEVC_264, newFileName, videoFilePath);ProcessUtils.executeCommand(cmd, appConfig.getShowFFmpegLog());}public void convertVideo2Ts(File tsFolder, String videoFilePath) {final String CMD_TRANSFER_2TS = "ffmpeg -y -i \"%s\"  -vcodec copy -acodec copy -vbsf h264_mp4toannexb \"%s\"";final String CMD_CUT_TS = "ffmpeg -i \"%s\" -c copy -map 0 -f segment -segment_list \"%s\" -segment_time 10 %s/%%4d.ts";String tsPath = tsFolder + "/" + Constants.TS_NAME;//生成.tsString cmd = String.format(CMD_TRANSFER_2TS, videoFilePath, tsPath);ProcessUtils.executeCommand(cmd, appConfig.getShowFFmpegLog());//生成索引文件.m3u8 和切片.tscmd = String.format(CMD_CUT_TS, tsPath, tsFolder.getPath() + "/" + Constants.M3U8_NAME, tsFolder.getPath());ProcessUtils.executeCommand(cmd, appConfig.getShowFFmpegLog());//删除index.tsnew File(tsPath).delete();}public Integer getVideoInfoDuration(String completeVideo) {final String CMD_GET_CODE = "ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 \"%s\"";String cmd = String.format(CMD_GET_CODE, completeVideo);String result = ProcessUtils.executeCommand(cmd, appConfig.getShowFFmpegLog());if (StringTools.isEmpty(result)) {return 0;}result = result.replace("\n", "");return new BigDecimal(result).intValue();}
}

这段代码展示了一个Java类 FFmpegUtils,该类主要提供了一些关于视频和图片处理的功能,利用了FFmpeg工具进行视频转码、生成视频缩略图、获取视频编码格式等操作。下面我将逐个解释每个方法的功能及其实现。

1. 类级别注解和成员

java">@Component
public class FFmpegUtils {@Resourceprivate AppConfig appConfig;
}
  • @Component 注解表示该类是一个Spring Bean,Spring框架会自动扫描并注册它为一个Bean,允许依赖注入等Spring特性。
  • @Resource 注解表示 appConfig 将被自动注入。这通常用于从配置类中注入配置数据,如 appConfig 中可能存储了一些关于FFmpeg日志的配置信息。

2. createImageThumbnail 方法:生成图片缩略图

java">public void createImageThumbnail(String filePath) {final String CMD_CREATE_IMAGE_THUMBNAIL = "ffmpeg -i \"%s\" -vf scale=200:-1 \"%s\"";String cmd = String.format(CMD_CREATE_IMAGE_THUMBNAIL, filePath, filePath + Constants.IMAGE_THUMBNAIL_SUFFIX);ProcessUtils.executeCommand(cmd, appConfig.getShowFFmpegLog());
}
  • 功能:根据输入的图片路径,生成一个宽度为200像素的缩略图,保持比例。
  • 实现
    • 使用 FFmpeg 的 scale 滤镜来调整图片大小。
    • 通过 String.format 格式化命令字符串,%s 会被 filePath 和目标缩略图的文件路径替换(缩略图路径使用 Constants.IMAGE_THUMBNAIL_SUFFIX 后缀)。
    • ProcessUtils.executeCommand(cmd, appConfig.getShowFFmpegLog()) 执行命令,通过外部工具运行FFmpeg命令。appConfig.getShowFFmpegLog() 可能是一个布尔值,指示是否在日志中显示FFmpeg的执行输出。

3. getVideoCodec 方法:获取视频编码格式

java">public String getVideoCodec(String videoFilePath) {final String CMD_GET_CODE = "ffprobe -v error -select_streams v:0 -show_entries stream=codec_name \"%s\"";String cmd = String.format(CMD_GET_CODE, videoFilePath);String result = ProcessUtils.executeCommand(cmd, appConfig.getShowFFmpegLog());result = result.replace("\n", "");result = result.substring(result.indexOf("=") + 1);String codec = result.substring(0, result.indexOf("["));return codec;
}
  • 功能:通过 ffprobe 工具获取视频文件的编码格式(例如 H.264, HEVC 等)。
  • 实现
    • 使用 ffprobe 命令(FFmpeg的一个工具)分析视频文件,提取视频流的编码名称。
    • 通过 ProcessUtils.executeCommand(cmd, appConfig.getShowFFmpegLog()) 执行命令,得到执行结果。
    • 对结果进行字符串处理:
      • 移除换行符。
      • = 之后提取编码名称。
      • 去掉编码名称中可能的多余字符(如 [)。
    • 返回编码格式(如 h264)。

4. convertHevc2Mp4 方法:将HEVC格式视频转换为MP4格式

java">public void convertHevc2Mp4(String newFileName, String videoFilePath) {String CMD_HEVC_264 = "ffmpeg -i %s -c:v libx264 -crf 20 %s";String cmd = String.format(CMD_HEVC_264, newFileName, videoFilePath);ProcessUtils.executeCommand(cmd, appConfig.getShowFFmpegLog());
}
  • 功能:将HEVC(H.265)格式的视频转换为H.264编码的MP4格式。
  • 实现
    • 使用 ffmpeg 命令,将输入的视频(videoFilePath)转换为 libx264 编码,并输出为新的文件(newFileName)。
    • -crf 20 表示视频的质量控制参数,值越低质量越高(20-25是常见的范围)。
    • 执行转换命令。

5. convertVideo2Ts 方法:将视频转换为.ts格式,并分割为多个片段

java">public void convertVideo2Ts(File tsFolder, String videoFilePath) {final String CMD_TRANSFER_2TS = "ffmpeg -y -i \"%s\"  -vcodec copy -acodec copy -vbsf h264_mp4toannexb \"%s\"";final String CMD_CUT_TS = "ffmpeg -i \"%s\" -c copy -map 0 -f segment -segment_list \"%s\" -segment_time 10 %s/%%4d.ts";String tsPath = tsFolder + "/" + Constants.TS_NAME;//生成.tsString cmd = String.format(CMD_TRANSFER_2TS, videoFilePath, tsPath);ProcessUtils.executeCommand(cmd, appConfig.getShowFFmpegLog());//生成索引文件.m3u8 和切片.tscmd = String.format(CMD_CUT_TS, tsPath, tsFolder.getPath() + "/" + Constants.M3U8_NAME, tsFolder.getPath());ProcessUtils.executeCommand(cmd, appConfig.getShowFFmpegLog());//删除index.tsnew File(tsPath).delete();
}
  • 功能:将视频文件转换为 .ts 格式并分割为多个片段,生成 .m3u8 索引文件,通常用于直播或点播视频流。
  • 实现
    • 生成 .ts 文件:使用 ffmpeg 命令将输入视频转为 .ts 格式,并使用 -vcodec copy -acodec copy 保持视频和音频流的原始格式,不重新编码。
    • 分割为多个片段:使用 ffmpeg-f segment 选项将 .ts 文件切割为多个片段,每片段的时长为 10 秒。
    • 删除临时文件:删除中间生成的 index.ts 文件,只保留最终的切片和 .m3u8 文件。

6. getVideoInfoDuration 方法:获取视频的时长

java">public Integer getVideoInfoDuration(String completeVideo) {final String CMD_GET_CODE = "ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 \"%s\"";String cmd = String.format(CMD_GET_CODE, completeVideo);String result = ProcessUtils.executeCommand(cmd, appConfig.getShowFFmpegLog());if (StringTools.isEmpty(result)) {return 0;}result = result.replace("\n", "");return new BigDecimal(result).intValue();
}
  • 功能:获取视频文件的时长(秒数)。
  • 实现
    • 使用 ffprobe 工具来获取视频的时长,命令中的 -show_entries format=duration 表示只获取时长信息。
    • 执行命令后,处理返回结果:
      • 去掉换行符。
      • 使用 BigDecimal 转换为整数返回时长(秒)。

总结

这段代码封装了一些FFmpeg工具的常用操作,如生成视频缩略图、转换视频格式、获取视频编码信息、将视频分割成.ts文件等。每个方法都通过 ProcessUtils.executeCommand 执行外部命令,调用FFmpeg和FFprobe工具来完成相应的功能。appConfig 用于控制FFmpeg命令的日志输出等配置。

ProcessUtils

java">import com.easylive.exception.BusinessException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;public class ProcessUtils {private static final Logger logger = LoggerFactory.getLogger(ProcessUtils.class);private static final String osName = System.getProperty("os.name").toLowerCase();public static String executeCommand(String cmd, Boolean showLog) throws BusinessException {if (StringTools.isEmpty(cmd)) {return null;}Runtime runtime = Runtime.getRuntime();Process process = null;try {//判断操作系统if (osName.contains("win")) {process = Runtime.getRuntime().exec(cmd);} else {process = Runtime.getRuntime().exec(new String[]{"/bin/sh", "-c", cmd});}// 执行ffmpeg指令// 取出输出流和错误流的信息// 注意:必须要取出ffmpeg在执行命令过程中产生的输出信息,如果不取的话当输出流信息填满jvm存储输出留信息的缓冲区时,线程就回阻塞住PrintStream errorStream = new PrintStream(process.getErrorStream());PrintStream inputStream = new PrintStream(process.getInputStream());errorStream.start();inputStream.start();// 等待ffmpeg命令执行完process.waitFor();// 获取执行结果字符串String result = errorStream.stringBuffer.append(inputStream.stringBuffer + "\n").toString();// 输出执行的命令信息if (showLog) {logger.info("执行命令{}结果{}", cmd, result);}return result;} catch (Exception e) {logger.error("执行命令失败cmd{}失败:{} ", cmd, e.getMessage());throw new BusinessException("视频转换失败");} finally {if (null != process) {ProcessKiller ffmpegKiller = new ProcessKiller(process);runtime.addShutdownHook(ffmpegKiller);}}}/*** 在程序退出前结束已有的FFmpeg进程*/private static class ProcessKiller extends Thread {private Process process;public ProcessKiller(Process process) {this.process = process;}@Overridepublic void run() {this.process.destroy();}}/*** 用于取出ffmpeg线程执行过程中产生的各种输出和错误流的信息*/static class PrintStream extends Thread {InputStream inputStream = null;BufferedReader bufferedReader = null;StringBuffer stringBuffer = new StringBuffer();public PrintStream(InputStream inputStream) {this.inputStream = inputStream;}@Overridepublic void run() {try {if (null == inputStream) {return;}bufferedReader = new BufferedReader(new InputStreamReader(inputStream));String line = null;while ((line = bufferedReader.readLine()) != null) {stringBuffer.append(line);}} catch (Exception e) {logger.error("读取输入流出错了!错误信息:" + e.getMessage());} finally {try {if (null != bufferedReader) {bufferedReader.close();}if (null != inputStream) {inputStream.close();}} catch (IOException e) {logger.error("调用PrintStream读取输出流后,关闭流时出错!");}}}}
}```在Spring Boot应用程序中,`FFmpeg` 是通过 `ProcessUtils` 类执行的,该类负责执行系统命令(如 `ffmpeg` 或 `ffprobe`)并处理其输出流和错误流。下面我会详细解释 `ProcessUtils` 类的工作原理以及它如何在后台运行 FFmpeg。### 1. **`executeCommand` 方法概述**
`executeCommand` 是 `ProcessUtils` 类的核心方法,它用于执行操作系统命令(比如 FFmpeg 命令),并返回执行的结果。```java
public static String executeCommand(String cmd, Boolean showLog) throws BusinessException {// 检查输入的命令是否为空if (StringTools.isEmpty(cmd)) {return null;}// 获取当前操作系统名称Runtime runtime = Runtime.getRuntime();Process process = null;try {// 根据操作系统类型执行不同的命令if (osName.contains("win")) {process = Runtime.getRuntime().exec(cmd); // Windows 系统} else {process = Runtime.getRuntime().exec(new String[]{"/bin/sh", "-c", cmd}); // Linux 或 macOS}// 获取并处理输出流和错误流PrintStream errorStream = new PrintStream(process.getErrorStream());PrintStream inputStream = new PrintStream(process.getInputStream());// 启动两个线程,分别读取标准输出流和错误输出流errorStream.start();inputStream.start();// 等待命令执行完毕process.waitFor();// 获取命令的输出结果String result = errorStream.stringBuffer.append(inputStream.stringBuffer + "\n").toString();// 如果 `showLog` 为 true,则记录日志if (showLog) {logger.info("执行命令{}结果{}", cmd, result);}// 返回执行的结果return result;} catch (Exception e) {logger.error("执行命令失败cmd{}失败:{} ", cmd, e.getMessage());throw new BusinessException("视频转换失败"); // 捕获异常并抛出自定义异常} finally {// 执行完毕后,注册一个退出钩子,确保进程在 JVM 退出时被销毁if (null != process) {ProcessKiller ffmpegKiller = new ProcessKiller(process);runtime.addShutdownHook(ffmpegKiller);}}
}

2. 方法执行过程解释

  • 检查输入命令是否为空:首先,方法检查传入的命令字符串(cmd)是否为空,如果为空则直接返回 null,避免执行空命令。

  • 获取系统信息:通过 System.getProperty("os.name").toLowerCase() 获取操作系统的名称,并将其转换为小写。这是为了决定在不同的操作系统(如 Windows 或 Linux)上如何执行命令。

  • 执行命令

    • Windows:如果操作系统是 Windows,直接调用 Runtime.getRuntime().exec(cmd) 执行命令。
    • Unix 系统(Linux/macOS):如果操作系统是 Linux 或 macOS,则使用 /bin/sh 来执行命令,/bin/sh -c 使得传入的字符串命令能够在 shell 中执行。
  • 输出和错误流处理:FFmpeg 执行命令时,会产生标准输出(stdout)和错误输出(stderr)。这些输出流需要被读取并处理,否则在输出流缓冲区满时,进程会阻塞。

    • 使用两个 PrintStream 线程分别读取 process.getErrorStream()process.getInputStream() 流。
  • 等待命令执行完毕:通过 process.waitFor() 阻塞当前线程,直到命令执行完毕。

  • 收集命令结果:命令执行完毕后,输出流中的数据会被拼接起来形成完整的执行结果。

  • 记录日志:如果 showLog 参数为 true,会将命令及其执行结果记录到日志中。

  • 异常处理:如果在执行命令过程中发生任何异常,会被捕获并记录错误日志,并抛出一个 BusinessException 异常,提示 “视频转换失败”。

  • 进程销毁:通过 ProcessKiller 类注册一个 JVM 退出钩子,在应用退出时确保 FFmpeg 进程被销毁,以防止后台进程继续运行。

3. 流的读取和关闭:PrintStream

PrintStream 是一个线程类,用于读取 FFmpeg 命令执行过程中的标准输出流和错误输出流。它是一个内部类,负责从 InputStream(标准输出流或错误输出流)中读取数据并将其存储到 StringBuffer 中。

java">static class PrintStream extends Thread {InputStream inputStream = null;BufferedReader bufferedReader = null;StringBuffer stringBuffer = new StringBuffer();public PrintStream(InputStream inputStream) {this.inputStream = inputStream;}@Overridepublic void run() {try {if (null == inputStream) {return;}bufferedReader = new BufferedReader(new InputStreamReader(inputStream));String line = null;while ((line = bufferedReader.readLine()) != null) {stringBuffer.append(line); // 将每一行的输出内容追加到 stringBuffer}} catch (Exception e) {logger.error("读取输入流出错了!错误信息:" + e.getMessage());} finally {try {if (null != bufferedReader) {bufferedReader.close();}if (null != inputStream) {inputStream.close();}} catch (IOException e) {logger.error("调用PrintStream读取输出流后,关闭流时出错!");}}}
}
  • 读取输出流PrintStream 通过 BufferedReader 逐行读取输入流(标准输出流或错误输出流),并将读取的内容存储到 StringBuffer 中。
  • 关闭流:在读取完毕后,它会关闭输入流和缓冲流,确保资源被正确释放。

4. 进程终止:ProcessKiller

ProcessKiller 类是一个自定义线程,它负责在应用程序退出时终止 FFmpeg 进程。

java">private static class ProcessKiller extends Thread {private Process process;public ProcessKiller(Process process) {this.process = process;}@Overridepublic void run() {this.process.destroy(); // 销毁进程}
}
  • 作用:当 JVM 退出时,ProcessKiller 会被触发,调用 process.destroy() 方法强制终止 FFmpeg 进程,避免进程僵死或长时间占用资源。

总结

通过 ProcessUtils.executeCommand 方法,Spring Boot 后端应用可以在运行时启动一个外部进程(如 FFmpeg),执行视频转换或其他处理任务。命令执行过程中产生的标准输出和错误输出流会被独立的线程读取并保存,最后将执行结果返回给调用者。为了保证系统的稳定性,ProcessUtils 还通过 ProcessKiller 线程确保 FFmpeg 进程在应用退出时被正确销毁。


http://www.ppmy.cn/devtools/144820.html

相关文章

梯度(Gradient)和 雅各比矩阵(Jacobian Matrix)的区别和联系:中英双语

雅各比矩阵与梯度:区别与联系 在数学与机器学习中,梯度(Gradient) 和 雅各比矩阵(Jacobian Matrix) 是两个核心概念。虽然它们都描述了函数的变化率,但应用场景和具体形式有所不同。本文将通过…

【Spring】探秘 SpringBoot 配置文件:解锁验证码背后的实现逻辑

前言 🌟🌟本期讲解关于Spring IOC&DI的详细介绍~~~ 🌈感兴趣的小伙伴看一看小编主页:GGBondlctrl-CSDN博客 🔥 你的点赞就是小编不断更新的最大动力 🎆那么…

数据结构---------二叉树前序遍历中序遍历后序遍历

以下是用C语言实现二叉树的前序遍历、中序遍历和后序遍历的代码示例&#xff0c;包括递归和非递归&#xff08;借助栈实现&#xff09;两种方式&#xff1a; 1. 二叉树节点结构体定义 #include <stdio.h> #include <stdlib.h>// 二叉树节点结构体 typedef struct…

使用Redis实现限流

使用Redis实现限流的三种方式 目录 概述基于计数器的固定窗口限流 实现原理适用场景实现步骤代码实现缺点 基于滑动窗口的限流 实现原理适用场景实现步骤代码实现优点缺点 基于令牌桶算法的限流 实现原理适用场景实现步骤Lua脚本实现Java实现优点缺点 总结 概述 在分布式系统…

45.跳跃游戏Ⅱ python

跳跃游戏Ⅱ 题目题目描述示例 1:示例 2:提示: 题解解决方案&#xff1a;贪心算法提交结果 题目 题目描述 给定一个长度为 n 的 0 索引整数数组 nums。初始位置为 nums[0]。 每个元素 nums[i] 表示从索引 i 向前跳转的最大长度。换句话说&#xff0c;如果你在 nums[i] 处&…

黑客如何找到App中的源IP:原理与防范

在移动互联网时代&#xff0c;应用程序&#xff08;App&#xff09;已经成为人们生活中不可或缺的一部分。然而&#xff0c;随着App的广泛应用&#xff0c;安全问题也日益受到关注。其中&#xff0c;源IP泄露是一个潜在的安全风险&#xff0c;可能导致服务器遭受攻击、敏感信息…

线程安全与线程不安全

线程安全的概念 线程安全&#xff1a;指当多个线程并发访问某个对象时&#xff0c;不会因为线程调度导致数据的不一致或数据污染&#xff0c;即能保证数据的完整性和正确性。实现线程安全的方式&#xff1a; 使用同步机制&#xff08;如 synchronized 关键字或显式锁 Reentran…

机器学习之偏差

机器学习中的偏差&#xff08;Bias&#xff09;是指模型的预测值与真实值之间的系统性误差&#xff0c;或者说模型无法准确捕捉数据中复杂模式的能力。偏差通常与模型的假设或学习能力有关&#xff0c;过高的偏差会导致模型的性能不佳&#xff0c;表现为欠拟合。 偏差的来源 模…