前话
怎样播放amr音频?这个问题让我好烦恼,在网上找了一些资料,quicktime插件虽然可以播放amr格式的音频,但是不满足项目的要求,html5也不能播放amr格式的音频。后来想到将amr音频转成其他HTML5支持的格式不久行了,后来在网上找到JAVE
能转换音频和视频,但是我在转换的过程中老是报如下的异常:
it.sauronsoftware.jave.EncoderException.EncoderException:Duration: N/A, bitrate: N/A
上面报的异常让我摸不着头脑,不知道是什么意思,后来经过研究
JAVE的源代码发现
JAVAE内部其实是使用
FFMPEG来进行转换,其实就是用java来调用ffmpeg.exe来进行转换(windows下是ffmpeg.exe文件,linux下是ffmpeg文件),然后通过解析转换过程中的输出语句来获取一些信息。后来我自己在window8下通过命令行来进行转换,能转换成功,而且支持的格式也很多。通过仔细的研究转换过程中输出的语句,我终于找到了产生上面异常的原因:在音频或视频的转换过程中,JAVAE有一段通过正则表达式来获取时长,开始时间和比特率的代码,而该正则表达式不能匹配到。
Duration: N/A, bitrate: N/A,而JAVE的这段代码(在it.sauronsoftware.jave.Encoder#public void encode(File source, File target, EncodingAttributes attributes, EncoderProgressListener listener) 中):
if (step == 0) {
if (line.startsWith("WARNING: ")) {
if (listener != null) {
listener.message(line);
}
} else if (!line.startsWith("Output #0")) {
throw new EncoderException(line);
} else {
step++;
}
}
从上面的代码可以看出如果是第0步解析到的某行输出不是以
Output #0开头,那么就抛出异常,实际上此时这行的值为Duration: N/A, bitrate: N/A,所以就抛出了如上的异常,从这里也可以看出JAVE是有BUG的:如果通过FFMPEG获取不到时长、开始时间和比特率,那么就会抛出异常,修改上面的配置正则表达式就能修复上面的BUG。实际上JAVE已经很久没维护了,下面进行amr音频格式转换就不使用JAVE,我自己简单的封装一下,可以根据实际的需求进行处理。
实现过程
本文是将amr文件转成mp3文件,然后输出到浏览器,思路:通过过滤器拦截以amr结尾的请求,对请求的路径进行处理,获取到文件所在的真实位置,如果文件不存在则让请求通过,如果存在则找同名的mp3文件,如果同名的mp3文件不存在则将amr转成mp3文件,并以相同的名字以mp3为后缀存储。设置相应的类型为MP3的MIME类型,读取mp3文件并输出。
package cn.zq.amrplay.web.filter;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import cn.zq.amrplay.util.AudioUtils;
/**
* <p>此过滤器用来拦截所有以amr后缀结尾的请求,并转换成mp3流输出,输出<strong>MIME</strong>类型为audio/mpeg。</p>
* @author Riccio Zhang
*
*/
public class Amr2Mp3Filter implements Filter{
/**
* mp3扩展名对应的MIME类型,值为"audio/mpeg"
*/
public final static String MP3_MIME_TYPE = "audio/mpeg";
public void init(FilterConfig filterConfig) throws ServletException {}
public void destroy() {}
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain filterChain)
throws IOException, ServletException {
HttpServletRequest request;
HttpServletResponse response;
try {
request = (HttpServletRequest) req;
response = (HttpServletResponse) resp;
} catch (ClassCastException e) {
throw new ServletException("non-HTTP request or response");
}
String requstURI = request.getRequestURI();
String contextPath = request.getContextPath();
String resPath = requstURI;
//去掉requstURI中contextPath部分和参数部分
if(contextPath.length() > 0) {
resPath = resPath.substring(contextPath.length());
}
int index = 0;
if((index = resPath.lastIndexOf("?")) != -1) {
resPath = resPath.substring(0, index);
}
String resRealPath = req.getServletContext().getRealPath(resPath);
String mp3ResRealPath = resRealPath.replaceFirst(".amr$", ".mp3");
File mp3File = new File(mp3ResRealPath);
if(!mp3File.exists()) {
File amrFile = new File(resRealPath);
if(!amrFile.exists()) {
filterChain.doFilter(request, response);
return;
}
AudioUtils.amr2mp3(amrFile.getAbsolutePath(), mp3File.getAbsolutePath());
}
response.setContentLength((int)mp3File.length());
response.setContentType(MP3_MIME_TYPE);
InputStream in = new FileInputStream(mp3File);
OutputStream out = response.getOutputStream();
try {
byte[] buf = new byte[1024];
int len = -1;
while((len = in.read(buf)) != -1) {
out.write(buf, 0, len);
}
} finally {
try {
in.close();
} catch (Exception e) {
e.printStackTrace();
}
out.flush();
}
}
}
上面过滤器的实现与上面的思路是吻合的。
音频转换的工具类:
package cn.zq.amrplay.util;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
public class AudioUtils {
/**
* ffmpeg.exe文件所在的路径
*/
private final static String FFMPEG_PATH;
static {
FFMPEG_PATH = AudioUtils.class.getResource("ffmpeg.exe").getFile();
}
/**
* 将一个amr文件转换成mp3文件
* @param amrFile
* @param mp3File
* @throws IOException
*/
public static void amr2mp3(String amrFileName, String mp3FileName) throws IOException {
Runtime runtime = Runtime.getRuntime();
Process process = runtime.exec(FFMPEG_PATH + " -i "+amrFileName+" -ar 8000 -ac 1 -y -ab 12.4k " + mp3FileName);
InputStream in = process.getErrorStream();
BufferedReader br = new BufferedReader(new InputStreamReader(in));
try {
String line = null;
while((line = br.readLine())!=null) {
System.out.println(line);
}
if(process.exitValue() != 0 ) {
//如果转换失败,这里需要删除这个文件(因为有时转换失败后的文件大小为0)
new File(mp3FileName).delete();
throw new RuntimeException("转换失败!");
}
} finally {
//为了避免这里抛出的异常会覆盖上面抛出的异常,这里需要用捕获异常。
try {
in.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
上面的工具类
amr2mp3方法,通过
java.lang.Runtime类来执行
ffmpeg.exe文件,在其后加上一系列的参数了(这个命令类似:ffmpeg -i f:\2.mp3 -ar 8000 -ac 1 -ab 12.2k f:\2.amr),并通过
process.getErrorStream()
(注意:process.getInputStream()并不能读取到任何输出,这有点奇怪,却要通过错误流才能读取到输出)方法通过流来读取转换过程中的输出,将其包装成了
BufferedReader以便每次读取一行,上面只是简单的讲输出打印到了控制台,最后通过判断程序退出值来判断是否转换成功,如果以退出值等于0则表示转换成功,否则抛出异常,删除mp3文件,最后关闭流。
下面简单说明下ffmpeg的几个命令参数:
- -i :指定输入文件
- -ar : 指定sampling rate(采样率),它的单位是HZ
- -ac:指定声道,1表示双声道,0表示单声道
- -ab:指定转换后的比特率
PS: 写到这里我才发现,这个工具类有点问题,由于项目代码是提前上传的,请将项目代码里的工具类替换为上面的代码。
过滤器配置:
<filter>
<filter-name>Amr2mp3Filter</filter-name>
<filter-class>cn.zq.amrplay.web.filter.Amr2Mp3Filter</filter-class>
</filter>
<filter-mapping>
<filter-name>Amr2mp3Filter</filter-name>
<url-pattern>*.amr</url-pattern>
</filter-mapping>
看一下效果:
资源链接:
- 本文示例代码下载地址:http://download.csdn.net/detail/qq791967024/9194647
- JAVE官网地址:http://www.sauronsoftware.it/projects/jave/(有下载地址,官方文档和例子)
- JAVE源码和jar包下载地址一:http://www.sauronsoftware.it/projects/jave/download.php?PHPSESSID=cgoi3vth901u5bsnemkej1h7p0(官方下载地址,速度非常慢)
- JAVE源码和jar包下载地址二:http://sourceforge.net/projects/jave/files/1.0.2/(下载速度比较快)
- ffmpeg官网地址:http://www.ffmpeg.org/(有windows,linux,MAC三个版本,还有一些文档,不过全是英文看起来有点费劲)
- ffmpeg中文资源一:http://bbs.chinavideo.org/archiver/?fid-10.html
- ffmpeg中文资源二:http://blog.csdn.net/hemingwang0902/article/details/4382429(这系列的文章还不错,不过有些不适合我们看)