使用servlet过滤器播放amr音频

来源:互联网 发布:c语言入门pdf下载 编辑:程序博客网 时间:2024/05/28 20:18

前话 


       怎样播放amr音频?这个问题让我好烦恼,在网上找了一些资料,quicktime插件虽然可以播放amr格式的音频,但是不满足项目的要求,html5也不能播放amr格式的音频。后来想到将amr音频转成其他HTML5支持的格式不久行了,后来在网上找到JAVE能转换音频和视频,但是我在转换的过程中老是报如下的异常:

[plain] view plain copy
  1. 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) 中):
[java] view plain copy
  1. if (step == 0) {  
  2.     if (line.startsWith("WARNING: ")) {  
  3.         if (listener != null) {  
  4.             listener.message(line);  
  5.         }  
  6.     } else if (!line.startsWith("Output #0")) {  
  7.         throw new EncoderException(line);  
  8.     } else {  
  9.         step++;  
  10.     }  
  11. }  
从上面的代码可以看出如果是第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文件并输出。

[java] view plain copy
  1. package cn.zq.amrplay.web.filter;  
  2.   
  3. import java.io.File;  
  4. import java.io.FileInputStream;  
  5. import java.io.IOException;  
  6. import java.io.InputStream;  
  7. import java.io.OutputStream;  
  8.   
  9. import javax.servlet.Filter;  
  10. import javax.servlet.FilterChain;  
  11. import javax.servlet.FilterConfig;  
  12. import javax.servlet.ServletException;  
  13. import javax.servlet.ServletRequest;  
  14. import javax.servlet.ServletResponse;  
  15. import javax.servlet.http.HttpServletRequest;  
  16. import javax.servlet.http.HttpServletResponse;  
  17.   
  18. import cn.zq.amrplay.util.AudioUtils;  
  19.   
  20. /** 
  21.  * <p>此过滤器用来拦截所有以amr后缀结尾的请求,并转换成mp3流输出,输出<strong>MIME</strong>类型为audio/mpeg。</p> 
  22.  * @author Riccio Zhang 
  23.  * 
  24.  */  
  25. public class Amr2Mp3Filter implements Filter{  
  26.       
  27.     /** 
  28.      * mp3扩展名对应的MIME类型,值为"audio/mpeg" 
  29.      */  
  30.     public final static String MP3_MIME_TYPE = "audio/mpeg";  
  31.       
  32.     public void init(FilterConfig filterConfig) throws ServletException {}  
  33.     public void destroy() {}  
  34.   
  35.     public void doFilter(ServletRequest req, ServletResponse resp, FilterChain filterChain)  
  36.             throws IOException, ServletException {  
  37.         HttpServletRequest  request;  
  38.         HttpServletResponse response;  
  39.         try {  
  40.             request = (HttpServletRequest) req;  
  41.             response = (HttpServletResponse) resp;  
  42.         } catch (ClassCastException e) {  
  43.             throw new ServletException("non-HTTP request or response");  
  44.         }  
  45.           
  46.         String requstURI = request.getRequestURI();  
  47.         String contextPath = request.getContextPath();  
  48.         String resPath = requstURI;  
  49.         //去掉requstURI中contextPath部分和参数部分  
  50.         if(contextPath.length() > 0) {  
  51.             resPath = resPath.substring(contextPath.length());  
  52.         }  
  53.         int index = 0;  
  54.         if((index = resPath.lastIndexOf("?")) != -1) {  
  55.             resPath = resPath.substring(0, index);  
  56.         }  
  57.           
  58.         String resRealPath = req.getServletContext().getRealPath(resPath);  
  59.         String mp3ResRealPath = resRealPath.replaceFirst(".amr$"".mp3");  
  60.         File mp3File = new File(mp3ResRealPath);  
  61.         if(!mp3File.exists()) {  
  62.             File amrFile = new File(resRealPath);  
  63.             if(!amrFile.exists()) {  
  64.                 filterChain.doFilter(request, response);  
  65.                 return;  
  66.             }  
  67.             AudioUtils.amr2mp3(amrFile.getAbsolutePath(), mp3File.getAbsolutePath());  
  68.         }  
  69.         response.setContentLength((int)mp3File.length());  
  70.         response.setContentType(MP3_MIME_TYPE);  
  71.         InputStream in = new FileInputStream(mp3File);  
  72.         OutputStream out = response.getOutputStream();  
  73.         try {  
  74.             byte[] buf = new byte[1024];  
  75.             int len = -1;  
  76.             while((len = in.read(buf)) != -1) {  
  77.                 out.write(buf, 0, len);  
  78.             }  
  79.         } finally {  
  80.             try {  
  81.                 in.close();  
  82.             } catch (Exception e) {  
  83.                 e.printStackTrace();  
  84.             }  
  85.             out.flush();  
  86.         }  
  87.     }  
  88.   
  89. }  
上面过滤器的实现与上面的思路是吻合的。

音频转换的工具类:

[java] view plain copy
  1. package cn.zq.amrplay.util;  
  2.   
  3. import java.io.BufferedReader;  
  4. import java.io.File;  
  5. import java.io.IOException;  
  6. import java.io.InputStream;  
  7. import java.io.InputStreamReader;  
  8.   
  9.   
  10. public class AudioUtils {  
  11.     /** 
  12.      * ffmpeg.exe文件所在的路径 
  13.      */  
  14.     private final static String FFMPEG_PATH;  
  15.     static {  
  16.         FFMPEG_PATH = AudioUtils.class.getResource("ffmpeg.exe").getFile();  
  17.     }  
  18.     /** 
  19.      * 将一个amr文件转换成mp3文件 
  20.      * @param amrFile  
  21.      * @param mp3File  
  22.      * @throws IOException  
  23.      */  
  24.     public static void amr2mp3(String amrFileName, String mp3FileName) throws IOException {  
  25.         Runtime runtime = Runtime.getRuntime();  
  26.         Process process = runtime.exec(FFMPEG_PATH + " -i "+amrFileName+" -ar 8000 -ac 1 -y -ab 12.4k " + mp3FileName);  
  27.         InputStream in = process.getErrorStream();  
  28.         BufferedReader br = new BufferedReader(new InputStreamReader(in));  
  29.         try {  
  30.             String line = null;  
  31.             while((line = br.readLine())!=null) {  
  32.                 System.out.println(line);  
  33.             }  
  34.             if(process.exitValue() != 0 ) {  
  35.                 //如果转换失败,这里需要删除这个文件(因为有时转换失败后的文件大小为0)  
  36.                 new File(mp3FileName).delete();  
  37.                 throw new RuntimeException("转换失败!");  
  38.             }  
  39.         } finally {  
  40.             //为了避免这里抛出的异常会覆盖上面抛出的异常,这里需要用捕获异常。  
  41.             try {  
  42.                 in.close();  
  43.             } catch (Exception e) {  
  44.                 e.printStackTrace();  
  45.             }  
  46.         }  
  47.     }  
  48. }  
上面的工具类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: 写到这里我才发现,这个工具类有点问题,由于项目代码是提前上传的,请将项目代码里的工具类替换为上面的代码。

过滤器配置:

[html] view plain copy
  1. <filter>  
  2.     <filter-name>Amr2mp3Filter</filter-name>  
  3.     <filter-class>cn.zq.amrplay.web.filter.Amr2Mp3Filter</filter-class>  
  4. </filter>  
  5. <filter-mapping>  
  6.     <filter-name>Amr2mp3Filter</filter-name>  
  7.     <url-pattern>*.amr</url-pattern>  
  8. </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(这系列的文章还不错,不过有些不适合我们看)