使用servlet過濾器播放amr音訊
阿新 • • 發佈:2019-01-03
前話
怎樣播放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檔案Duration: N/A, bitrate: N/A,而JAVE的這段程式碼(在it.sauronsoftware.jave.Encoder#public void encode(File source, File target, EncodingAttributes attributes, EncoderProgressListener listener) 中):
從上面的程式碼可以看出如果是第0步解析到的某行輸出不是以Output #0開頭,那麼就丟擲異常,實際上此時這行的值為Duration: N/A, bitrate: N/A,所以就丟擲瞭如上的異常,從這裡也可以看出JAVE是有BUG的:如果通過FFMPEG獲取不到時長、開始時間和位元率,那麼就會丟擲異常,修改上面的配置正則表示式就能修復上面的BUG。實際上JAVE已經很久沒維護了,下面進行amr音訊格式轉換就不使用JAVE,我自己簡單的封裝一下,可以根據實際的需求進行處理。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++; } }
實現過程
本文是將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>
看一下效果: