视频存储部署
一、环境安装
1.1 yasm安装
在编译ffmpeg时,执行./configure,报出了如下错误:
“nasm/yasm not found or too old. Use –disable-x86asm for a crippled build.”
需安装yasm解决:
1)可下载安装
1.在http://www.tortall.net/projects/yasm/releases/上下载适合自己的版本
2.解压并进入yasm的目录
3.执行./configure
4.执行make
5.执行sudo make install
安装成功后再在ffmpeg下执行configure即可通过
2)命令行安装
apt-get install yasm/ yum install yasm
1.2 ffmpeg安装
1)可下载安装
1、在官网下载对应版本的ffmpeg
2、解压安装
tar -zxvf ffmpeg-4.4.2.tar.gz
cd ffmpeg-4.4.2
./configure --prefix=/usr/local/ffmpeg
make && make install
3、配置变量
vi /etc/profile
在最后PATH添加环境变量:
export PATH=$PATH:/usr/local/ffmpeg/bin
保存退出
查看是否生效
source /ect/profile 设置生效
4、查看版本
ffmpeg -version 查看版本
出现“gcc is unable to create an executable file C compiler test failed.”
安装 yum install gccy
好了之后执行cd /usr/local/ffmpeg进入安装目录,查看一下发现有bin,include,lib,share这4个目录,其中bin是ffmpeg主程序二进制目录,include是C/C++头文件目录,lib是编译好的库文件目录,share是文档目录,然后进入bin目录,执行 ./ffmpeg -version 查看当前版本的详细信息,默认情况下一般会报libavdevice.so.57: cannot open shared object file: No such file or directory,原因是lib目录未加载到链接到系统库中,添加库:
添加这些库:vim /etc/ld.so.conf
然后添加一行内容: /usr/local/ffmpeg/lib
之后保存并退出,
然后执行 :sudo ldconfig 重新加载资源
使配置生效,现在再次执行 ./ffmpeg -version 显示就正常了
二、FfmpegUtils工具
2.1 FfmpegUtils代码
import com.zdxf.videostore.utils.video.VideoDesc;
import lombok.extern.slf4j.Slf4j;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @Author: zj
* @Date: 2022/3/31 0031 15:40
* @Description:
*/
@Slf4j
public class FfmpegUtils {
/**
* 设置图片大小
*/
private final static String IMG_SIZE = "1920x1080";
/**
* ffmpeg执行目录
*/
private final String ffmpegPath;
/**
* 正则表达式,获取视频的时长、比特率
*/
private static final Pattern VIDEO_PATTERN = Pattern.compile("Duration: (.*?), start: (.*?), bitrate: (\\d*) kb\\/s");
public FfmpegUtils(String ffmpegPath) {
this.ffmpegPath = ffmpegPath;
}
/**
* 合并视频
* @param txtPath txtPath
* @param newFilePath 新文件路径
*/
public void videoMerge(String txtPath,String newFilePath) {
// 最好 ffmpeg -f concat -i filelist.txt -c copy output.mkv
List<String> commands = new ArrayList<>();
commands.add(ffmpegPath);
commands.add("-f");
commands.add("concat");
commands.add("-safe");
commands.add("0");
commands.add("-i");
commands.add(txtPath);
commands.add("-c");
commands.add("copy");
commands.add("-strict");
commands.add("-2");
commands.add(newFilePath);
log.info("合成视频开始,{},命令:{}", newFilePath, String.join(" ", commands));
execAndWriteInfo(commands, "videoMerge");
log.info("合成视频结束,{}", newFilePath);
}
/**
* 执行命令并打印执行信息
* @param commands 命令
* @param methodName 方法名
* @return 命令执行信息
*/
private static String execAndWriteInfo(List<String> commands, String methodName) {
ProcessBuilder builder = new ProcessBuilder().command(commands);
StringBuilder sb = new StringBuilder();
try {
Process process = builder.start();
BufferedReader br = new BufferedReader(new InputStreamReader(process.getErrorStream()));
String line;
while ((line = br.readLine()) != null) {
sb.append(line);
}
br.close();
return sb.toString();
} catch (Exception ex) {
ex.printStackTrace();
log.error("{},err:{}, info:{}",methodName, ex.getMessage(), sb);
}
log.info("{},info:{}",methodName, sb);
return "";
}
/**
* 视频获取截图
* @param videoPath 视频地址
* @param imagePath 图片保存地址
* @param timePoint 截取时间点 秒
*/
public void videoToImage(String videoPath, String imagePath, int timePoint) {
List<String> commands = new ArrayList<>();
commands.add(ffmpegPath);
commands.add("-ss");
// 这个参数是设置截取视频多少秒时的画面
commands.add(timePoint + "");
commands.add("-i");
commands.add(videoPath);
commands.add("-y");
commands.add("-f");
commands.add("image2");
commands.add("-t");
commands.add("0.001");
commands.add("-s");
//这个参数是设置截取图片的大小
commands.add(IMG_SIZE);
commands.add(imagePath);
log.info("开始截图:{},命令:{}", videoPath, String.join(" ", commands));
execAndWriteInfo(commands, "videoToImage");
log.info("完成截图:{}", videoPath);
}
/**
* 获取视频时长
* @param videoPath 视频路径
* @return 时长
*/
public VideoDesc getVideoDesc(String videoPath) {
List<String> commands = new java.util.ArrayList<>();
commands.add(ffmpegPath);
commands.add("-i");
commands.add(videoPath);
log.info("获取视频时长开始:{},命令:{}", videoPath, String.join(" ", commands));
try {
String info = execAndWriteInfo(commands, "getVideoDesc");
//从视频信息中解析时长
Matcher m = VIDEO_PATTERN.matcher(info);
if (m.find()) {
return new VideoDesc(0, 0, m.group(3), videoPath.split("\\.")[1], m.group(1).split("\\.")[0], videoPath);
}
log.error("获取视频时长完成e:{}", info);
} catch (Exception e) {
e.printStackTrace();
}
log.info("获取视频时长完成:{}", videoPath);
return null;
}
/**
* 转换格式
* @param videoPath 视频路径
* @param outPath 输出路径
*/
public boolean videoChangeFormat(String videoPath, String outPath) {
// ffmpeg -f h264 -i 1.dav -c copy test.mp4
// `ffmpeg -i "车载录像20150320.dav" -vcodec copy -acodec copy "车载录像20150320.mp4"`
List<String> commands = new ArrayList<>();
commands.add(ffmpegPath);
// commands.add("-f");
// commands.add("-h264");
commands.add("-i");
commands.add(videoPath);
commands.add("-vcodec");
commands.add("copy");
commands.add("-acodec");
commands.add("copy");
commands.add(outPath);
log.info("转换格式开始:{},命令:{}", videoPath, String.join(" ", commands));
execAndWriteInfo(commands, "videoChangeFormat");
log.info("转换格式完成:{}", videoPath);
return false;
}
/**
* 视频抽取音频文件
* @param videoPath 视频路径
* @param type 文件类型
* @param audioPath 文件保存路径
* @return 是否成功
*/
public boolean videoToAudio(String videoPath, String type, String audioPath){
List<String> commands = new ArrayList<>();
commands.add(ffmpegPath);
commands.add("-i");
commands.add(videoPath);
commands.add("-f");
commands.add(type);
commands.add("-vn");
commands.add("-y");
commands.add("-acodec");
if("wav".equals(type)){
commands.add("pcm_s16le");
}else if("mp3".equals(type)){
commands.add("mp3");
}
commands.add("-ar");
commands.add("16000");
commands.add("-ac");
commands.add("1");
commands.add(audioPath);
try {
ProcessBuilder builder = new ProcessBuilder();
builder.command(commands);
Process p = builder.start();
// 1. start
// 保存ffmpeg的输出结果流
BufferedReader buf;
String line;
buf = new BufferedReader(new InputStreamReader(p.getInputStream()));
StringBuffer sb = new StringBuffer();
while ((line = buf.readLine()) != null) {
System.out.println(line);
sb.append(line);
}
// 这里线程阻塞,将等待外部转换进程运行成功运行结束后,才往下执行
p.waitFor();
// 1. end
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* wav 转 mp3
* wav转mp3命令:ffmpeg -i test.wav -f mp3 -acodec libmp3lame -y wav2mp3.mp3
* @param wavPath wav路径
* @param mp3Path MP3路径
* @return 是否成功
*/
public boolean ffmpegOfWavToMp3(String wavPath, String mp3Path){
List<String> commands = new java.util.ArrayList<>();
commands.add(ffmpegPath);
commands.add("-i");
commands.add(wavPath);
commands.add("-f");
commands.add("mp3");
commands.add("-acodec");
commands.add("libmp3lame");
commands.add("-y");
commands.add(mp3Path);
try {
// 1. start
// 保存ffmpeg的输出结果流
ProcessBuilder builder = new ProcessBuilder();
builder.command(commands);
Process p = builder.start();
BufferedReader buf = new BufferedReader(new InputStreamReader(p.getInputStream()));
String line;
StringBuilder sb = new StringBuilder();
while ((line = buf.readLine()) != null) {
sb.append(line);
}
// 这里线程阻塞,将等待外部转换进程运行成功运行结束后,才往下执行
p.waitFor();
// 1. end
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
public static void main(String[] args) throws InterruptedException {
String path = "ffmpeg";
String outputPath = "E:\\video\\0000000001210\\0000000001210.mp4";
String filePath1 = "E:\\video\\0000000001005\\0000000001005_task990-648.flv";
String outPath1 = "E:\\video\\0000000001005\\0000000001005_task990-648.mp4";
String imagePath = "E:\\video\\0000000001005\\0000000001005.jpg";
String txtFilePath = "E:\\video\\0000000001005\\0000000001005.txt";
// FfmpegUtils.videoMerge(txtFilePath, outputPath);
//FfmpegUtils.videoToImage(outputPath, imagePath, 0);
FfmpegUtils ffmpegUtils = new FfmpegUtils("ffmpeg");
// ffmpegUtils.videoChangeFormat(filePath1, outPath1);
//TimeUnit.MILLISECONDS.sleep(240);
System.out.println(ffmpegUtils.getVideoDesc(outputPath));
}
}
2.2 springboot配置使用
@Slf4j
@Service
public class VideoStoreServiceImpl implements VideoStoreService {
private final FfmpegUtils ffmpegUtils;
public VideoStoreServiceImpl(@Value("${ffmpeg.path}") String ffmpegPath) {
this.ffmpegUtils = new FfmpegUtils(ffmpegPath);
ffmpegUtils.videoMerge(txtFilePath, outputPath);
}
}