怎样播放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有一段通过正则表达式来获取时长,开始时间和比特率的代码,而该正则表达式不能匹配到。
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的几个命令参数:
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>
看一下效果: