此文章基于dadiyang/jave
借鉴jave拷贝可执行文件代码修改后gitee传送门
项目修改后,为执行命令的方式,封装了几个常用工具类,暂不支持进度
ywyh/ffmpeg项目使用示例
amr转mp3
public static void main(String[] args) throws IOException, InterruptedException {
AudioUtil.amrToMp3(new File("a.amr"), new File("./a.mp3"));
}
通过m3u8链接下载mp4视频
通过m3u8连接下载视频,并合并为mp4文件
public static void main(String[] args) throws IOException, InterruptedException {
VideoUtil.downloadByM3u8ToMp4("m3u8 url", new File("./test.mp4"));
}
mp4转m3u8
将mp4装为m3u8,使用nginx,hls可以实现进度条快进
public static void main(String[] args) throws IOException, InterruptedException {
VideoUtil.mp4ToM3u8(new File("./t.mp4"), new File("m3u8/t"));
}
截图(每秒截几张图)
每秒截0.1张图,就是10秒截一张图
public static void main(String[] args) throws IOException, InterruptedException {
VideoUtil.thumbnailByfps(new File("t.mp4"), new File("img/t"), 0.1F);
}
截图(按时间截图)
在视频第10秒的时候截一张图
public static void main(String[] args) throws IOException, InterruptedException {
VideoUtil.thumbnailByTime(new File("t.mp4"), new File("img/t"), 10);
}
jave源码分析
注意:jave中,使用的linux ffmpeg可执行二进制文件有问题,目前使用官网最新版替换,在centos7.6上运行正常
在工作中,基于微信开发,遇到一个问题,就是微信公众号发送的语音是amr格式,音频有效期内可以使用serverId进行播放(发送的语音都是临时文件,有效期为3天),但是小程序无法使用serverId进行播放,包括js也无法简单的播放amr格式的音频文件,故需求转换为mp3格式的音频文件,以便统一各个平台的语音播放。
基于jave mp4转m3u8,使用jave的时候,发现里面并没有次方法,故而仔细阅读了一下jave的源码,在源码的基础上做了如下修改
- 开放FFMPEGExecutor类和FFMPEGLocator类的createExecutor方发
- 新增FormatEnum枚举类,定义各个格式类型转换需要设定的参数,在Encoder类的encode方法执行ffmpeg.execute()前,执行FormatEnum.get(formatAttribute).handle(ffmpeg, target),
- 添加对应的执行命令
VideoUtils 新增m3u8方法
后续详细介绍这几个方法的作用以及用法
源码分析:
本人修改后的源码地址 github传送门
AudioUtils -> amrToMp3方法为原自带方法,通过此入口,找到了根执行文件
-
AudioUtils -> amrToMp3
public static void amrToMp3(File source, File target) { convert(source, target, "mp3"); }```
-
AudioUtils -> convert
在此方法上,设置了基础的格式信息等
public static void convert(File source, File target, String format) { AudioAttributes audio = new AudioAttributes(); Encoder encoder = new IgnoreErrorEncoder(); audio.setCodec(LIBMP_3_LAME); EncodingAttributes attrs = new EncodingAttributes(); attrs.setFormat(format); attrs.setAudioAttributes(audio); try { encoder.encode(source, target, attrs); } catch (Exception e) { throw new IllegalStateException("convert amr to " + format + " error: ", e); } }```
-
Encoder -> encode
此方法根据EncodingAttributes下的AudioAttributes和VideoAttributes设置的参数,封装ffmpeg的执行命令
此方法过长,不贴源码了,请大家自行去看 -
FFMPEGExecutor -> execute
大家可以看到,最终框架依然使用的是
Runtime runtime = Runtime.getRuntime();
执行。public void execute() throws IOException { int argsSize = args.size(); String[] cmd = new String[argsSize + 1]; cmd[0] = ffmpegExecutablePath; for (int i = 0; i < argsSize; i++) { cmd[i + 1] = (String) args.get(i); } Runtime runtime = Runtime.getRuntime(); log.info("exec cmd: {}", Arrays.toString(cmd)); ffmpeg = runtime.exec(cmd); ffmpegKiller = new ProcessKiller(ffmpeg); runtime.addShutdownHook(ffmpegKiller); inputStream = ffmpeg.getInputStream(); outputStream = ffmpeg.getOutputStream(); errorStream = ffmpeg.getErrorStream(); }
看到这里大家应该已经有了一个清晰的思路。
在使用中,本来不想改源码,基于框架内置的ffmpeg命令进行转换,但是发现,怎么都无法获取地址
原因如下:public abstract class FFMPEGLocator { protected abstract String getFFMPEGExecutablePath(); FFMPEGExecutor createExecutor() { return new FFMPEGExecutor(getFFMPEGExecutablePath()); } }
看这段源码可以看到,内置的ffmpeg命令执行路径,被隐藏了,只能同包内引用。
后准备使用内置的创建命令,查看源码后,发现FFMPEGLocator类是public,故准备执行new FFMPEGLocator();
来创建对象获取ffmpeg命令执行路径,看源码后,发现次方法暴露的依然是protected,故最终对源码进行了修改,打包并发布到maven私服上。
本次修改,新增了FormatEnum枚举类,在Encoder -> encode方法执行是,执行FormatEnum.get(formatAttribute).handle(ffmpeg, target);
,
def 默认执行类,在类型无法匹配或不存在时,执行此方法(兼容原来的代码,不需要在方法修改后去修改之前的项目代码)
ssegment 当使用此编码格式的时候,添加执行的命令集(此处因为没有做更多的测试,仅仅满足了我自己的项目要求,如有更多需求,可自行修改或添加更多格式)
public enum FormatEnum {
def(){
@Override
public void handle(FFMPEGExecutor ffmpeg, File target) {
ffmpeg.addArgument("-y");
ffmpeg.addArgument(target.getAbsolutePath());
}
},
ssegment(){
@Override
public void handle(FFMPEGExecutor ffmpeg, File target) {
ffmpeg.addArgument("-map");
ffmpeg.addArgument("0");
ffmpeg.addArgument("-segment_format");
ffmpeg.addArgument("mpegts");
ffmpeg.addArgument("-segment_list");
if(!target.getParentFile().exists())target.getParentFile().mkdirs();
ffmpeg.addArgument(target.getAbsolutePath());//拼接存放目录
ffmpeg.addArgument("-segment_time");
ffmpeg.addArgument("10");
ffmpeg.addArgument(target.getParent() + File.separator + "%03d.ts");//拼接存放目录
}
};
FormatEnum() {}
public abstract void handle(FFMPEGExecutor ffmpeg, File target);
public static FormatEnum get(String name){
if(name == null || name.isEmpty()) return def;
try{
return FormatEnum.valueOf(name);
}catch (IllegalArgumentException e){
return def;
}
}
public static void main(String[] args) {
System.out.println(FormatEnum.get("ssegment"));
}
}
因为使用的不是很多,也不是专门做这块的,故做的修改比较简单。如有其他需求,可评论留言一起交流讨论。
如文章没有介绍清楚,可留言,我会尽快完善。谢谢大家