【JAVA】使用百度語音識別 Rest API,遇到識別結果顯示亂碼的問題和解決

JoKKKKKKKKKKKKK發表於2020-12-18


遇到亂碼問題

在使用百度語音識別 JAVA Rest API 的時候,把應用部署到外部Tomcat,
發現返回的語音識別結果是亂碼,而在IDEA上測試API,返回的結果正常。


百度語音識別 Rest API

短語音識別 簡介
GitHub地址

處理、上傳檔案,獲取識別結果。過程:

com/baidu/speech/restapi/asrdemo/ AsrMain.java

public static void main(String[] args) throws IOException, DemoException {
        AsrMain demo = new AsrMain();
        String result = demo.run();
        // 列印識別結果result
        log.info("識別結束:結果是:");
        log.info(result);
    }

public String run() throws IOException, DemoException {
		...
       	// 以Json方式上傳檔案,獲取返回的字串
            result = runJsonPostMethod(token);
        ...
        return result;
    }

// 預設以Json方式上傳檔案
// 注:本文作者新增:傳入了引數 WAV檔案路徑+檔名
private String runJsonPostMethod(String token, String fileName) throws IOException, DemoException {         
	...
    byte[] content = getFileContent(fileName);
    String speech = base64Encode(content);
    ...
    // 從HttpURLConnection 獲取返回的字串
	String result = ConnUtil.getResponseString(conn);
	...
	return result;
}

// 將檔案讀取到FileInputStream,作為bytes返回
private byte[] getFileContent(String fileName) throws IOException{
    File file = new File(fileName);
    if (!file.canRead()) {
        log.error("檔案不存在或者不可讀: " + file.getAbsolutePath());
    }
    try(FileInputStream fileInputStream = new FileInputStream(file)) {
        // 將InputStream內的內容全部讀取,作為bytes返回
        return ConnUtil.getInputStreamContent(fileInputStream);
    } catch (Exception e) {
        log.error("讀取檔案到輸入流過程錯誤:" + e.getMessage(), e);
        return null;
    }
}

com/baidu/speech/restapi/common/ ConnUtil.java:

    // 從HttpURLConnection 獲取返回的字串
    public static String getResponseString(HttpURLConnection conn) throws IOException {
        // 從連線資訊返回的內容
        return new String(getResponseBytes(conn));
    }

	// 從HttpURLConnection 獲取返回的 bytes
    public static byte[] getResponseBytes(HttpURLConnection conn) throws IOException, DemoException {
        int responseCode = conn.getResponseCode();
        InputStream inputStream = conn.getInputStream();
        ...
        byte[] result = getInputStreamContent(inputStream);
        return result;
    }

    // 將InputStream內的內容全部讀取,作為bytes返回
    public static byte[] getInputStreamContent(InputStream is) throws IOException {
        byte[] b = new byte[1024];
        // 定義一個輸出流儲存接收到的資料
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        // 開始接收資料
        int len = 0;
        while (true) {
            len = is.read(b);
            if (len == -1) {
                // 資料讀完
                break;
            }
            byteArrayOutputStream.write(b, 0, len);
        }
        return byteArrayOutputStream.toByteArray();
    }

Rest API 的使用

String myApiKey = "myApiKey";
String mySecretKey = "mySecretKey";
String fileName = "D:/test.wav";
// 獲得token: TokenHolder為API的 common 包中的類
TokenHolder holder =
        new TokenHolder(myApiKey, mySecretKey, "audio_voice_assistant_get");
holder.resfresh();
String token = holder.getToken();
// 預設以json方式上傳音訊檔案
String result = runJsonPostMethod(token, fileName);

// 解析返回的json字串
JsonParser jsonParser = new JsonParser();
// 將json字串轉化成json物件
JsonObject jsonObject = jsonParser.parse(result).getAsJsonObject();

// 返回結果中的錯誤碼。若錯誤碼為0,則識別成功。
String stringErrorNumber = jsonObject.get("err_no").getAsString();
// 返回結果中的語音識別文欄位落
String stringErrorMessage = jsonObject.get("err_msg").getAsString();

int errorNumber;
if(stringErrorNumber != null) {
	errorNumber = Integer.valueOf(stringErrorNumber);
} else {
	errorNumber = 0;
}
// 根據錯誤碼,進行簡單分類
switch (errorNumber) {
	case 0:     // 識別成功
	    log.info("識別結束,結果是:" + jsonObject.get("result").getAsString());
	    break;
	case 3307:  // recognition error 識別出錯,無法識別
		// TO-DO
	    break;
	case 3308:  // speech too long 音訊檔案時長過
	    // TO-DO 
	    break;
	default:	// 識別不成功
	    log.error("百度ASR結果錯誤:" + wavFullName
	        + ",錯誤程式碼:" + errorNumber
	        + ",錯誤資訊:" + stringErrorMessage);
}

亂碼現象和解決過程

1. 亂碼現象

用1個時長不到60s的,符合百度短語音識別 REST API 格式的,WAV檔案,用IDEA測試,結果:IDEA測試
可見,result 裡邊顯示的是正常的中文識別結果。

打包應用,部署到外部Tomcat,執行測試,也識別成功,日誌顯示卻是亂碼:
識別結果亂碼

2. 解決過程

嘗試修改Tomcat編碼,改 UTF-8,或者改GBK,都無效;
解決各種tomcat中文亂碼問題
修復tomcat9.0中文亂碼問題
tomcat配置及中文亂碼問題的解決方案

在JAVA程式碼中,用 String(str.getBytes(“GBK”), “UTF-8”); 也無效:
知乎:java中GBK編碼格式轉成UTF8,用一段方法實現怎麼做?

public static String recover(String str) throws Throwable {
	return new String(str.getBytes("GBK"), "UTF-8");
}

使用後,依舊亂碼:

String jsonBack = new String(jsonBack.getBytes("GBK"), StandardCharsets.UTF_8);
log.info("ASR結果: " + jsonBack);

然後,某天在網上看到這兩個圖:
常見亂碼問題和產生原因
聊天記錄
搜了一下,發現相關文章:
[轉載]字元亂碼說明

看上去,本文遇上的是第一種情況:以GBK方式讀取UTF-8編碼的中文,百度API返回的結果就是UTF-8格式,這應該不用懷疑的,在IDEA內部Tomcat的UTF-8環境下測試都顯示正常。但嘗試過修改了外部Tomcat的編碼,沒用。乾脆從JAVA程式碼切入,看看能不能修改一點程式碼就能解決。

在獲取識別的返回結果中,尋找能夠設定字串String編碼格式的地方。從最底層的
getResponseBytes(HttpURLConnection conn) 和 getInputStreamContent(InputStream is) 看起,兩個函式中沒發現字串String。

繼續往上,發現 getResponseString(HttpURLConnection conn) 裡邊有 new String:

// 從HttpURLConnection 獲取返回的字串
public static String getResponseString(HttpURLConnection conn) throws IOException {
    // 從連線資訊返回的內容
    return new String(getResponseBytes(conn));
}

於是,直接嘗試加入UTF-8編碼格式:

public static String getResponseString(HttpURLConnection conn) throws IOException {
    // 從連線資訊返回的內容,先用UTF-8解碼
    return new String(getResponseBytes(conn), StandardCharsets.UTF_8);
}

部署到外部Tomcat測試,結果顯示正常!!!

成功

相關文章