1. 程式人生 > >使用servlet過濾器播放amr音訊

使用servlet過濾器播放amr音訊

前話 

       怎樣播放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>

看一下效果:

效果

資源連結: