Android 科大訊飛語音評測SDK 踩坑實錄

英語付費類APP大多都會對用戶的發音進行評測的場景,一些大公司借住其高效的語音識別技術可以很輕鬆的實現。我司最開始接入的是騰訊雲智聆SDK,但是用戶反映普遍較爲激烈,我們不堪其擾,於是在最新的版本中將其切換爲科大訊飛SDK。

第一步,當然是登陸科大訊飛官網,開始註冊賬號,創建APP,本地記錄下APPID,並下載相應的SDK。需要注意的是,必須下載appid對應的sdk,下載之後,需要將項目中的jar放在libs下,so庫放在jniLibs下,避免出錯。

第二部當然就是在本地運行科大訊飛所寫的示例demo。這個時候,你會發現運行不起來。

不需要慌張,科大訊飛官網上有相應的帖子 http://bbs.xfyun.cn/forum.php?mod=viewthread&tid=42878&extra=可以解決,根據帖子提示,在apply添加byildscript{}

第三步,我們可以去通過訊飛demo來初步體驗語音評測了。

第四步,我們項目中已經成功接入了SDK,現在需要對錄音評測的代碼進行相關封裝,方便多個地方的代碼調用。這一步也是最爲重要的一點。

4.1 SDK初始化

 SpeechUtility.createUtility(this, SpeechConstant.APPID + "=APPID");
 SpeechEvaluator evaluator = SpeechEvaluator.createEvaluator(this, new InitListener() {
            @Override
            public void onInit(int i) {
            }
        });
KDflyUtils.init(evaluator);

注意,這一部分代碼是放在應用啓動時,我們創建了全局唯一的一個錄音對象,並通過KDflyUtils(語音評測工具類) 初始化,將錄音對象傳入工具類,方便後續調用

4.2 現在,我們來看一下封裝的工具類。工具類裏面我們需要具備幾個作用:開始評測,結束評測,評測狀態(是否正在評測),以及評測結果(成功/失敗)的回調。

 public static void init(SpeechEvaluator eva) {
        evaluator = eva;
    }

    /**
     * @return
     */
    public static boolean isRecording() {
        return evaluator == null ? false : evaluator.isEvaluating();
    }

    private static void setParams() {
        if (evaluator == null) {
            LogUtils.i("初始化失敗");
            return;
        }
        evaluator.setParameter(SpeechConstant.LANGUAGE, "en_us");
        evaluator.setParameter(SpeechConstant.ISE_CATEGORY, "read_sentence");
        evaluator.setParameter(SpeechConstant.TEXT_ENCODING, "utf-8");
        evaluator.setParameter(SpeechConstant.VAD_BOS, "5000");
        evaluator.setParameter(SpeechConstant.VAD_EOS, "18000");
        evaluator.setParameter(SpeechConstant.KEY_SPEECH_TIMEOUT, "-1");
//        evaluator.setParameter(SpeechConstant.RESULT_LEVEL, "complete");
        evaluator.setParameter(SpeechConstant.RESULT_LEVEL, "plain");
        evaluator.setParameter(SpeechConstant.AUDIO_FORMAT_AUE, "opus");
        // 設置音頻保存路徑,保存音頻格式支持pcm、wav,設置路徑爲sd卡請注意WRITE_EXTERNAL_STORAGE權限
        evaluator.setParameter(SpeechConstant.AUDIO_FORMAT, "wav");
        evaluator.setParameter(SpeechConstant.ISE_AUDIO_PATH, path);
    }

init()方法即爲我們在Application裏嗲用過的初始化方法。isRecording用於判斷是否正在錄音,setParams()方法用於設定錄音的相關參數,其中的path是我們在本地存放的錄音的路徑。

然後是開始錄音的方法:方法極爲簡單,只需要傳入一個需要評測的文本字符串,以及錄音監聽對象即可。

/**
*對錄音過程進行監聽
*/ 
private static EvaluatorListener evaluatorListener = new EvaluatorListener() {
        @Override
        public void onVolumeChanged(int i, byte[] bytes) {

        }

        @Override
        public void onBeginOfSpeech() {
            LogUtils.i("onBeginOfSpeech:");
        }

        @Override
        public void onEndOfSpeech() {
            LogUtils.i("onEndOfSpeech:");
        }

        @Override
        public void onResult(EvaluatorResult result, boolean isLast) {
            if (isLast) {
         
//              評測結束 解析結果
            }
               
        }

        @Override
        public void onError(SpeechError speechError) {
            LogUtils.i("onError:" + speechError.getErrorDescription() + " ______" + speechError.toString());
        }

        @Override
        public void onEvent(int i, int i1, int i2, Bundle bundle) {
            LogUtils.i("onEvent:" + i + "," + i1 + "," + i2);
        }
    };  


/**
     * @param ecText 評測文本
     */
    public static void startScore(String ecText) {
        //開始評測
        setParams();
        int i = evaluator.startEvaluating(ecText, null, evaluatorListener);
        LogUtils.i("語音評測:" + i);
    }

然後是結束錄音的方法:

  public static void stopRecording() {
        evaluator.stopEvaluating();
    }

再之後我們需要在監聽裏處理評測結果:onResult()    返回結果爲xml格式的,因此我們通過Pull解析結果。獲得評分,並保留2位小數。我寫的回調方法是把錄音文件轉換爲byte[],和評分結果返回。失敗時,直接回調失敗方法。

     @Override
public void onResult(EvaluatorResult result, boolean isLast) {
            LogUtils.i("onResult:" + isLast + "," + result.getResultString() + "," + result.describeContents());
            if (isLast) {
//              評測結束 解析結果 <?xml version="1.0" ?><FinalResult><ret value="0"/><total_score value="2.687397"/></FinalResult>
                XmlPullParser xmlPullParser = Xml.newPullParser();
                try {
                    xmlPullParser.setInput(new ByteArrayInputStream(result.getResultString().getBytes()), "utf-8");
                    int eventType = xmlPullParser.getEventType();
                    while (eventType != XmlPullParser.END_DOCUMENT) {
                        switch (eventType) {
                            case XmlPullParser.START_TAG:
                                String name = xmlPullParser.getName();
                                if (name.equals("total_score")) {
                                    float v = Float.parseFloat(xmlPullParser.getAttributeValue(0));
                                    String format = new DecimalFormat("0.00").format(v);
                                    LogUtils.i("評測結果:" + format);
                                    RandomAccessFile r = null;
                                    try {
                                        r = new RandomAccessFile(path, "r");
                                        audioData = new byte[(int) r.length()];
                                        r.readFully(audioData);
                                    } catch (FileNotFoundException e) {
                                        e.printStackTrace();
                                    } catch (Exception e) {
                                        e.printStackTrace();
                                    } finally {
                                        if (r != null) {
                                            r.close();
                                        }
                                    }
                                    evaluateResult.onResultSuccess(audioData, Float.parseFloat(format));
                                }
                                break;
                        }
                        eventType = xmlPullParser.next();
                    }

                } catch (Exception e) {
                    e.printStackTrace();//發生異常
                    evaluateResult.onResultFail("數據異常");
                }
            }
        }

最後看一下回調接口:

這樣,整個公路類的封裝就已經結束了,在代碼中要如何調用呢?

 /**
     * 新版科大訊飛語音評測
     */
    private void hasPermiassionRecord() {
        if (!KDevaluateUtils.isRecording()) {
         //entitle 是需要評測的問題
            KDevaluateUtils.startScore(enTitle);

            KDevaluateUtils.setEvaluateResult(new EvaluateResult() {
                @Override
                public void onResultSuccess(byte[] audioData, float score) {
                    //評測成功
                }

                @Override
                public void onResultFail(String errorMsg) {
                    showInfo("網絡異常,請切換網絡試一下");
                }
            });

        } else {
            KDevaluateUtils.stopRecording();
        }
    }

很簡單,是不是?

最後附上全部代碼:

/**
 * 科大訊飛語音評測
 * Created by zhangyanpeng on 2020/4/8
 */
public class KDflyUtils {

    private static SpeechEvaluator evaluator;

    private static EvaluateResult evaluateResult;

    public static void setEvaluateResult(EvaluateResult evaluateResult) {
        KDflyUtils.evaluateResult = evaluateResult;
    }

    //  本地文件路徑
    private static String path = Environment.getExternalStorageDirectory() + "/AnnieRecord/annie.wav";
    private static EvaluatorListener evaluatorListener = new EvaluatorListener() {
        @Override
        public void onVolumeChanged(int i, byte[] bytes) {

        }

        @Override
        public void onBeginOfSpeech() {
            LogUtils.i("onBeginOfSpeech:");
        }

        @Override
        public void onEndOfSpeech() {
            LogUtils.i("onEndOfSpeech:");
        }

        @Override
        public void onResult(EvaluatorResult result, boolean isLast) {
            if (isLast) {
                LogUtils.i("onResult:" + isLast + "," + result.getResultString() + "," + result.describeContents());
//              評測結束 解析結果
                XmlPullParser xmlPullParser = Xml.newPullParser();
                try {
                    xmlPullParser.setInput(new ByteArrayInputStream(result.getResultString().getBytes()), "utf-8");
                    int eventType = xmlPullParser.getEventType();
                    while (eventType != XmlPullParser.END_DOCUMENT) {
                        switch (eventType) {
                            case XmlPullParser.START_TAG:
                                String name = xmlPullParser.getName();
                                if (name.equals("total_score")) {
                                    float v = Float.parseFloat(xmlPullParser.getAttributeValue(0));
                                    String format = new DecimalFormat("0.00").format(v);
                                    LogUtils.i("評測結果:" + format);
                                    evaluateResult.onResultSuccess(new byte[1024],Float.parseFloat(format));
                                }
                                break;
                            case XmlPullParser.END_TAG:
                                break;
                        }
                        eventType = xmlPullParser.next();
                    }
                } catch (XmlPullParserException e) {
                    e.printStackTrace();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }

        @Override
        public void onError(SpeechError speechError) {
            LogUtils.i("onError:" + speechError.getErrorDescription() + " ______" + speechError.toString());
        }

        @Override
        public void onEvent(int i, int i1, int i2, Bundle bundle) {
            LogUtils.i("onEvent:" + i + "," + i1 + "," + i2);
        }
    };

    public static void init(SpeechEvaluator eva) {
        evaluator = eva;
    }

    /**
     * @return
     */
    public static boolean isRecording() {
        return evaluator == null ? false : evaluator.isEvaluating();
    }

    private static void setParams() {
        if (evaluator == null) {
            LogUtils.i("初始化失敗");
            return;
        }
        evaluator.setParameter(SpeechConstant.LANGUAGE, "en_us");
        evaluator.setParameter(SpeechConstant.ISE_CATEGORY, "read_sentence");
        evaluator.setParameter(SpeechConstant.TEXT_ENCODING, "utf-8");
        evaluator.setParameter(SpeechConstant.VAD_BOS, "5000");
        evaluator.setParameter(SpeechConstant.VAD_EOS, "18000");
        evaluator.setParameter(SpeechConstant.KEY_SPEECH_TIMEOUT, "-1");
//        evaluator.setParameter(SpeechConstant.RESULT_LEVEL, "complete");
        evaluator.setParameter(SpeechConstant.RESULT_LEVEL, "plain");
        evaluator.setParameter(SpeechConstant.AUDIO_FORMAT_AUE, "opus");
        // 設置音頻保存路徑,保存音頻格式支持pcm、wav,設置路徑爲sd卡請注意WRITE_EXTERNAL_STORAGE權限
        evaluator.setParameter(SpeechConstant.AUDIO_FORMAT, "wav");
        evaluator.setParameter(SpeechConstant.ISE_AUDIO_PATH, path);
    }

    //  開始評測
    public static void startScore(byte[] audio, String evText) {
        setParams();
        int ret = evaluator.startEvaluating(audio, null, evaluatorListener);
        if (ret != ErrorCode.SUCCESS) {
            LogUtils.i("識別失敗,錯誤碼:" + ret);
        } else {
//            在startEvaluating接口調用之後,加入以下方法,即可通過直接
            //寫入音頻的方式進行評測業務
            try {
                if (audioData.length == 0) {
                    file = new File(path);
                    audioData = new byte[(int) file.length()];
                    fileOutputStream = new FileOutputStream(path);
                    fileOutputStream.write(audioData, 0, (int) file.length());
                }

                //防止寫入音頻過早導致失敗
                try {
                    new Thread().sleep(100);
                } catch (InterruptedException e) {
                    LogUtils.i("文件讀取失敗");
                }
                evaluator.writeAudio(audioData, 0, audioData.length);
                evaluator.stopEvaluating();
            } catch (Exception e) {
            }
        }
    }

    private static FileOutputStream fileOutputStream;
    private static byte[] audioData;
    private static File file;

    public static void stopRecording() {
        evaluator.stopEvaluating();
        try {
            file = new File(path);
            audioData = new byte[(int) file.length()];
            fileOutputStream = new FileOutputStream(path);
            fileOutputStream.write(audioData, 0, (int) file.length());
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {

        }
    }

    /**
     * @param ecText 評測文本
     */
    public static void startScore(String ecText) {
        //開始評測
        setParams();
        int i = evaluator.startEvaluating(ecText, null, evaluatorListener);
        LogUtils.i("語音評測:" + i);
    }
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章