优雅地使用ffmpeg转换音频格式

最近发现项目中原来使用的ffmpeg转码音频格式(amr转为wav),总是报错(错误代码:Caused by: it.sauronsoftware.jave.InputFormatException),挺费解的,也找不到原因。

百度发现,对于这个问题的解答很少,试了很多办法都不靠谱。后来发现,有朋友封装过jave,可以完美解决,可以参照

https://www.cnblogs.com/livedian/p/11981232.html。

因此,我也引用jave这个jar(如下),先将amr转化为mp3,再将mp3转换为wav。奇怪的是,虽然转换没有报错,但是始终无法生成mp3文件。

<dependency>
    <groupId>com.github.dadiyang</groupId>
    <artifactId>jave</artifactId>
    <version>1.0.4</version>
</dependency>

对于这个问题我也研究了很久,发现这样始终读取不了jave里面封装的ffmpeg程序,只能读取原来项目存在的版本较老的程序,最后,索性直接将源码jave的源码下载下来,直接放到项目上配置使用,最后就没有问题了。

首先,下载jave源码的地址是GitHub:https://github.com/dadiyang/jave,在项目的java目录下创建包it.sauronsoftware.jave与jave里面的包名路径一样,避免重复修改类里面的包名,从而增加工作量。如图1。将jave中所有的类都复制到这个包下。
在这里插入图片描述
接下来的重点是要在it.sauronsoftware.jave.DefaultFFMPEGLocator.java配置ffmpeg程序的路径,让程序能够读取到ffmpeg这个程序进行转码。我是配置到项目下的bin目录下。


/*
* JAVE - A Java Audio/Video Encoder (based on FFMPEG)
*
* Copyright (C) 2008-2009 Carlo Pelliccia (www.sauronsoftware.it)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
package it.sauronsoftware.jave;


import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


import java.io.*;


/**
* The default ffmpeg executable locator, which exports on disk the ffmpeg
* executable bundled with the library distributions. It should work both for
* windows and many linux distributions. If it doesn't, try compiling your own
* ffmpeg executable and plug it in JAVE with a custom {@link FFMPEGLocator}.
*
* @author Carlo Pelliccia
*/
public class DefaultFFMPEGLocator extends FFMPEGLocator {
    private static final Logger log = LoggerFactory.getLogger(it.sauronsoftware.jave.DefaultFFMPEGLocator.class);
    /**
     * Trace the version of the bundled ffmpeg executable. It's a counter: every
     * time the bundled ffmpeg change it is incremented by 1.
     */
    private static final int MYEX_EVERSION = 1;
    private static final String WINDOWS = "windows";
    private static final String MAC = "mac";


    /**
     * The ffmpeg executable file path.
     */
    private String path;


    /**
     * It builds the default FFMPEGLocator, exporting the ffmpeg executable on a
     * temp file.
     */
    public DefaultFFMPEGLocator() {
        // Windows?
        boolean isWindows;
        boolean isMac = false;
        String ffmpegHome = null;
        String os = System.getProperty("os.name").toLowerCase();
        if (os.contains(WINDOWS)) {
            ffmpegHome = System.getProperty("user.dir") + "\\src\\main\\startup\\";
            isWindows = true;
        } else {
            ffmpegHome = System.getProperty("user.dir") + "/bin";  //这是linux下程序的bin目录路径
            isWindows = false;
            isMac = os.contains(MAC);
        }
        // Temp dir?
        File temp = null;
        if (ffmpegHome != null && !"".equals(ffmpegHome)) {
            log.info("ffmpeg.home: " + ffmpegHome);
            temp = new File(ffmpegHome);
        }
        if (temp == null || !temp.exists()) {
            temp = new File(System.getProperty("java.io.tmpdir"), "it.sauronsoftware.jave-"
                    + MYEX_EVERSION);
            log.info("ffmpeg.home does not exists, use default bin path: " + temp.getAbsolutePath());
        }
        if (!temp.exists()) {
            temp.mkdirs();
            temp.deleteOnExit();
        }
        // ffmpeg executable export on disk.
        String suffix = isWindows ? ".exe" : isMac ? "-mac" : "";
        File exe = new File(temp, "ffmpeg" + suffix);
        if (!exe.exists() || exe.length() <= 0) {
            copyFile("bin/ffmpeg" + suffix, exe);
        }
        // pthreadGC2.dll
        if (isWindows) {
            File dll = new File(temp, "pthreadGC2.dll");
            if (!dll.exists()) {
                copyFile("bin/pthreadGC2.dll", dll);
            }
        }
        // Need a chmod?
        if (!isWindows) {
            Runtime runtime = Runtime.getRuntime();
            try {
                runtime.exec(new String[]{"/bin/chmod", "755",
                        exe.getAbsolutePath()});
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        // Ok.
        this.path = exe.getAbsolutePath();
    }


    @Override
    protected String getFFMPEGExecutablePath() {
        return path;
    }


    /**
     * Copies a file bundled in the package to the supplied destination.
     *
     * @param path The name of the bundled file.
     * @param dest The destination.
     * @throws RuntimeException If aun unexpected error occurs.
     */
    private void copyFile(String path, File dest) throws RuntimeException {
        InputStream input = null;
        OutputStream output = null;
        try {
            input = getClass().getClassLoader().getResourceAsStream(path);
            output = new FileOutputStream(dest);
            byte[] buffer = new byte[1024];
            int l;
            while ((l = input.read(buffer)) != -1) {
                output.write(buffer, 0, l);
            }
        } catch (IOException e) {
            throw new RuntimeException("Cannot write file "
                    + dest.getAbsolutePath());
        } finally {
            if (output != null) {
                try {
                    output.close();
                } catch (Throwable ignored) {


                }
            }
            if (input != null) {
                try {
                    input.close();
                } catch (Throwable ignored) {
                }
            }
        }
        if (!dest.exists()) {
            String errMsg = "copy ffmpeg executable file to " + dest.getAbsolutePath() + " fail";
            log.info(errMsg);
            throw new IllegalStateException(errMsg);
        }
    }
}

接下来的重点是要在it.sauronsoftware.jave.DefaultFFMPEGLocator.java配置ffmpeg程序的路径,让程序能够读取到ffmpeg这个程序进行转码。我是配置到项目下的bin目录下。


/*
* JAVE - A Java Audio/Video Encoder (based on FFMPEG)
*
* Copyright (C) 2008-2009 Carlo Pelliccia (www.sauronsoftware.it)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
package it.sauronsoftware.jave;


import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


import java.io.*;


/**
* The default ffmpeg executable locator, which exports on disk the ffmpeg
* executable bundled with the library distributions. It should work both for
* windows and many linux distributions. If it doesn't, try compiling your own
* ffmpeg executable and plug it in JAVE with a custom {@link FFMPEGLocator}.
*
* @author Carlo Pelliccia
*/
public class DefaultFFMPEGLocator extends FFMPEGLocator {
    private static final Logger log = LoggerFactory.getLogger(it.sauronsoftware.jave.DefaultFFMPEGLocator.class);
    /**
     * Trace the version of the bundled ffmpeg executable. It's a counter: every
     * time the bundled ffmpeg change it is incremented by 1.
     */
    private static final int MYEX_EVERSION = 1;
    private static final String WINDOWS = "windows";
    private static final String MAC = "mac";


    /**
     * The ffmpeg executable file path.
     */
    private String path;


    /**
     * It builds the default FFMPEGLocator, exporting the ffmpeg executable on a
     * temp file.
     */
    public DefaultFFMPEGLocator() {
        // Windows?
        boolean isWindows;
        boolean isMac = false;
        String ffmpegHome = null;
        String os = System.getProperty("os.name").toLowerCase();
        if (os.contains(WINDOWS)) {
            ffmpegHome = System.getProperty("user.dir") + "\\src\\main\\startup\\";
            isWindows = true;
        } else {
            ffmpegHome = System.getProperty("user.dir") + "/bin";  //这是linux下程序的bin目录路径
            isWindows = false;
            isMac = os.contains(MAC);
        }
        // Temp dir?
        File temp = null;
        if (ffmpegHome != null && !"".equals(ffmpegHome)) {
            log.info("ffmpeg.home: " + ffmpegHome);
            temp = new File(ffmpegHome);
        }
        if (temp == null || !temp.exists()) {
            temp = new File(System.getProperty("java.io.tmpdir"), "it.sauronsoftware.jave-"
                    + MYEX_EVERSION);
            log.info("ffmpeg.home does not exists, use default bin path: " + temp.getAbsolutePath());
        }
        if (!temp.exists()) {
            temp.mkdirs();
            temp.deleteOnExit();
        }
        // ffmpeg executable export on disk.
        String suffix = isWindows ? ".exe" : isMac ? "-mac" : "";
        File exe = new File(temp, "ffmpeg" + suffix);
        if (!exe.exists() || exe.length() <= 0) {
            copyFile("bin/ffmpeg" + suffix, exe);
        }
        // pthreadGC2.dll
        if (isWindows) {
            File dll = new File(temp, "pthreadGC2.dll");
            if (!dll.exists()) {
                copyFile("bin/pthreadGC2.dll", dll);
            }
        }
        // Need a chmod?
        if (!isWindows) {
            Runtime runtime = Runtime.getRuntime();
            try {
                runtime.exec(new String[]{"/bin/chmod", "755",
                        exe.getAbsolutePath()});
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        // Ok.
        this.path = exe.getAbsolutePath();
    }


    @Override
    protected String getFFMPEGExecutablePath() {
        return path;
    }


    /**
     * Copies a file bundled in the package to the supplied destination.
     *
     * @param path The name of the bundled file.
     * @param dest The destination.
     * @throws RuntimeException If aun unexpected error occurs.
     */
    private void copyFile(String path, File dest) throws RuntimeException {
        InputStream input = null;
        OutputStream output = null;
        try {
            input = getClass().getClassLoader().getResourceAsStream(path);
            output = new FileOutputStream(dest);
            byte[] buffer = new byte[1024];
            int l;
            while ((l = input.read(buffer)) != -1) {
                output.write(buffer, 0, l);
            }
        } catch (IOException e) {
            throw new RuntimeException("Cannot write file "
                    + dest.getAbsolutePath());
        } finally {
            if (output != null) {
                try {
                    output.close();
                } catch (Throwable ignored) {


                }
            }
            if (input != null) {
                try {
                    input.close();
                } catch (Throwable ignored) {
                }
            }
        }
        if (!dest.exists()) {
            String errMsg = "copy ffmpeg executable file to " + dest.getAbsolutePath() + " fail";
            log.info(errMsg);
            throw new IllegalStateException(errMsg);
        }
    }
}

将原来jave封装的ffmpeg程序放到项目的bin目录下,如图2.
在这里插入图片描述
虽然现在放置的是在startup目录下,因为项目使用的是assembly插件进行打包的,打包后程序就会生成bin目录,源码中startup目录的文件都将会复制到bin目录下。

通过这一通的设置,我们就可以通过调用AudioUtils.amrToWav(source,target)方法,直接将amr格式音频转为wav,不用经过中间转换步骤。

package it.sauronsoftware.jave;


import java.io.File;


/**
* 音频转换工具
*
* @author dadiyang
* date 2018/12/14
*/
public class AudioUtils {


    private static final String LIBMP_3_LAME = "libmp3lame";


    /**
     * amr转mp3
     *
     * @param sourcePath 音频来源目录
     * @param targetPath 目标存放地址
     */
    public static void amrToMp3(String sourcePath, String targetPath) {
        File source = new File(sourcePath);
        File target = new File(targetPath);
        amrToMp3(source, target);
    }


    /**
     * amr转mp3
     *
     * @param source 音频来源
     * @param target 目标存放地址
     */
    public static void amrToMp3(File source, File target) {
        convert(source, target, "mp3");
    }


    /**s
     * amr转wav
     *
     * @param source 音频来源
     * @param target 目标存放地址
     */
    public static void amrToWav(File source, File target) {
        convert(source, target, "wav");
    }


    public static void convert(File source, File target, String format) {
        if (!source.exists()) {
            throw new IllegalArgumentException("source file does not exists: " + source.getAbsoluteFile());
        }
        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);
        }
    }
}

语音转化之所以会报那个错误,是因为使用的ffmpeg程序的版本太老,直接引入jave又导致无法读取封装的新版的ffmpeg,因此,只能自己对代码进行封装和配置了。最后完美解决了问题。

   对于jave这个工具类,其实早就有人封装过了,需要使用的话可以这样引入
<dependency>
    <groupId>jave</groupId>
    <artifactId>jave-linux</artifactId>
    <version>1.0.2.2</version>
</dependency>

封装的原理也是调用ffmpeg程序,进行多媒体格式转换。但是,一般不推荐这么引用,由于官方的jave(The JAVE (Java Audio Video Encoder) library is Java wrapper on the ffmpeg project)已经没有维护了,处于年久失修状态,调用的ffmpeg版本很低,存在很多问题,导致发生各式各样的错误。

我们自己封装jave,只是为了能够随意配置ffmpeg存放的目录和选择版本较新的ffmpeg程序。

今天自己写这边文章就是因为遇到了这方面的问题,网上虽然有解决办法,但很少有满意的。

虽然可以调用别人封装的包,但是由于和项目结构不一样等原因,导致无法调用别人封装到jar里面较新的ffmpeg程序,报错仍然发生。

因此,将这次遇到的问题和解决办法做个分享,希望能够帮助到有需要的人。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值