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

embedded/2024/12/23 20:58:21/

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/embedded/148168.html

相关文章

Docker核心概念总结

容器介绍 Docker 是世界领先的软件容器平台,所以想要搞懂 Docker 的概念我们必须先从容器开始说起。 什么是容器? 先来看看容器较为官方的解释 一句话概括容器:容器就是将软件打包成标准化单元,以用于开发、交付和部署。 容器镜像是轻量…

使用C语言连接MySQL

库的准备 要使用C语言连接mysql,需要使用mysql官网提供的connect库,可以去官网下载,由于我们要下载到 Linux 操作系统中,也可以使用如下指令进行安装库 sudo apt-get install libmysqlclient-dev MySQL连接C/C的库通常会安装在/us…

FPGA 16 ,Verilog中的位宽:深入理解与应用

目录 前言 一. 位宽的基本概念 二. 位宽的定义方法

【Mybatis-Plus】使用步骤 条件构造器 分页模型

文章目录 Mybatis-Plus使用步骤条件构造器分页模型 Mybatis-Plus MyBatis-Plus(简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。 需要快速搭建 CRUD 接口的应用程序。对于已…

【Linux打怪升级记 | 问题01】安装Linux系统忘记设置时区怎么办?3个方法教你回到东八区

🗺️博客地图 📍方法一、timedatectl 命令 📍方法二、手动链接 /etc/localtime 📍方法三、修改时区变量 在 Linux 系统中,可以通过以下3种方式将系统时区修改为 CST(中国标准时间,GMT8 或称 …

小程序快速实现大模型聊天机器人

需求分析: 基于大模型,打造一个聊天机器人;使用开放API快速搭建,例如:讯飞星火;先实现UI展示,在接入API。 最终实现效果如下: 一.聊天机器人UI部分 1. 创建微信小程序&#xff0c…

设计模式——建造者模式

设计模式——建造者模式 目录 设计模式——建造者模式介绍实现结构及工作流程经典实现示例优缺点使用场景 扩展实现示例 总结 介绍 建造者模式(Builder Pattern)是一种创建型设计模式,它通过将一个复杂对象的构建过程分解成多个简单的步骤&a…

如何深入学习JVM底层原理?

前言 对于Java虚拟机(JVM),我相信大多数人的学习模式都是在面试前夕才会临时抱佛脚,而在平时的工作中,对它的关注可能就略显冷淡了。我敢打赌,很多人的书架上,《深入理解Java虚拟机》第三版恐怕…