java调用ffmpeg转码

此文章基于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"));
    }
}

因为使用的不是很多,也不是专门做这块的,故做的修改比较简单。如有其他需求,可评论留言一起交流讨论。
如文章没有介绍清楚,可留言,我会尽快完善。谢谢大家

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值