最近发现项目中原来使用的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程序,报错仍然发生。
因此,将这次遇到的问题和解决办法做个分享,希望能够帮助到有需要的人。