科大訊飛語音無限制錄音、識別功能的實現:Android studio(一)

            最近想要做一款語音聽寫APP,在網上搜索關於如何使用科大訊飛語音的Demo少之又少,又或者是隻是單純的按照文檔來實現簡單的語音聽寫,遠遠不能滿足需求,看了幾天的文檔和自己搜索的一些資料,還有這幾天中遇到的一些問題,覺得有必要做一個筆記,能給初學者一些幫助,也順便理一下這些天的一些收穫,本人只是一個初學者,假如有寫得不對或者不好的地方,還望大家指出~~

1、首先當然是創建應用,我這裏只是使用了語音聽寫的功能,創建完成後下載SDK,打開是這樣子的


2、導入SDK:

將開發工具包中libs目錄下的Msc.jar和Sunflower.jar複製到Android工程的libs目錄中,將online文件夾裏面的子文件粘貼到工程目錄src/main/jniLibs(這是Android studio 和eclipse的不同之處)。假如你要使用它自帶的UI動畫對話框錄音,請將assets文件夾以及裏面的子文件粘貼到工程目錄src/main/下面,完成後如下圖:




3、添加權限:

<!--連接網絡權限,用於執行雲端語音能力 -->
<uses-permission android:name="android.permission.INTERNET"></uses-permission>
<!--獲取手機錄音機使用權限,聽寫、識別、語義理解需要用到此權限 -->
<uses-permission android:name="android.permission.RECORD_AUDIO"></uses-permission>
<!--讀取網絡信息狀態 -->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"></uses-permission>
<!--獲取當前wifi狀態 -->
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"></uses-permission>
<!--允許程序改變網絡連接狀態 -->
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"></uses-permission>
<!--讀取手機信息權限 -->
<uses-permission android:name="android.permission.READ_PHONE_STATE"></uses-permission>
<!--讀取聯繫人權限,上傳聯繫人需要用到此權限 -->
<uses-permission android:name="android.permission.READ_CONTACTS"></uses-permission>
<!--假如我們要保存錄音,還需要以下權限-->
<!-- 在SDCard中創建與刪除文件權限 -->
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"></uses-permission>
<!-- SD卡權限  -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"></uses-permission>
<!-- 允許程序讀取或寫入系統設置 -->
<uses-permission android:name="android.permission.WRITE_SETTINGS"></uses-permission>



4、初始化:

//將“12345678”替換成您申請的APPID,申請地址:http://open.voicecloud.cn

SpeechUtility.createUtility(this, "appid=123456789");

數據收集接口:

	
@Override
protected void onResume() {
    	// 開放統計 移動數據統計分析
    	FlowerCollector.onResume(MainActivity.this);
    	super.onResume();
}

@Override
protected void onPause() {
    	// 開放統計 移動數據統計分析
    	FlowerCollector.onPause(MainActivity.this);
    	super.onPause();
}

說明:
1.確保在所有的 activity 中都調用 FlowerCollector.onResume() FlowerCollector.onPause()方法。這兩個調用將不會阻塞應用程序的主線程,也不會影響應用程序的性能。
2.注意,如果您的 Activity 之間有繼承或者控制關係請不要同時在父和子 Activity 中重複添加onPause onResume 方法,否則會造成重複統計(eg:使用 TabHostTabActivityActivityGroup )3.一個應用程序在多個 activity 之間連續切換時,會被視爲同一個 session(啓動)4.當用戶兩次使用之間間隔超過 30 秒時,將被認爲是兩個的獨立的 session(啓動)。例如:用戶回到home,或進入其他程序,經過一段時間後再返回之前的應用。
5.所有日誌收集工作均在 onResume 之後進行,在 onPause 之後結束。 (ps:其實我也不懂這是幹嘛的,猜想是爲了收集數據的,還有一些參數設置我就不羅列了,有興趣的同學可以去看,因爲我發現就算沒設置它們我也能進行語音聽寫,反正收集的數據我看不懂)


5、語音聽寫的使用(採取雲端聽寫的引擎模式,暫時未考慮本地集成)

注意導包:com.iflytek.cloud而不是android.speech.SpeechRecognizer

1)使用自帶UI語音對話框

優點:簡單方便、美觀

缺點:端點超時會自動停止識別(不理解的往下看設置參數說明)默認前段點:5000 後端點:1800

這是使用語音識別最簡單的方法,在點擊事件上調用start()方法就好了

public void start() {
        //1.創建SpeechRecognizer對象,第二個參數:本地聽寫時傳InitListener
        iatDialog = new RecognizerDialog(this, initListener);
        //2.設置聽寫參數
        iatDialog.setParameter(SpeechConstant.DOMAIN, "iat");
        iatDialog.setParameter(SpeechConstant.LANGUAGE, "zh_cn");
        iatDialog.setParameter(SpeechConstant.ACCENT, "mandarin ");
        //3.設置回調接口
        iatDialog.setListener(new RecognizerDialogListener() {
            @Override
            public void onResult(RecognizerResult recognizerResult, boolean b) {
                if (!b) {
                    String json = recognizerResult.getResultString();
                    String str = JsonParser.parseIatResult(json);
                    System.out.println("說話內容:"+str);
                    textView.setText(str);
                }
            }

            @Override
            public void onError(SpeechError speechError) {
                Log.d("error", speechError.toString());
            }
        });
//4.開始聽寫
        iatDialog.show();
    }

增加if(!b)的判斷是因爲每次說話結束之後,返回數據的最後一次是返回一個標點符號"。"或者"!"之類的,當然你也可以設置不返回標點符號。

//設置是否帶標點符號 0表示不帶標點,1則表示帶標點。
mIat.setParameter(SpeechConstant.ASR_PTT, "0");

2)不使用自帶UI對話框

優點:可以設置自己想要的參數

缺點:使用比較麻煩,對於初學者來講,根據講話音量大小自定義麥克風效果的View簡直是噩夢(我不會,哪位大神做出來了求分享~~)

簡單的用法:

1.創建對象:

<span style="font-size:18px;">//1.創建SpeechRecognizer對象,第二個參數:本地聽寫時傳InitListener
mIat = SpeechRecognizer.createRecognizer(this, null);</span>

	2.設置參數(聽寫這三個參數是必須的,下面設置時不再提示):
mIat.setParameter(SpeechConstant.DOMAIN, "iat");
// 簡體中文:"zh_cn", 美式英文:"en_us"
mIat.setParameter(SpeechConstant.LANGUAGE, "zh_cn");
//普通話:mandarin(默認)
//粵 語:cantonese
//四川話:lmz
//河南話:henanese
mIat.setParameter(SpeechConstant.ACCENT, "mandarin ");
3.實例化監聽對象
	
private RecognizerListener recognizerListener = new RecognizerListener() {
        @Override
        public void onVolumeChanged(int i, byte[] bytes) {

        }

        @Override
        public void onBeginOfSpeech() {
            System.out.println("開始識別");
        }

        @Override
        public void onEndOfSpeech() {
            System.out.println("識別結束");
        }

        @Override
        public void onResult(RecognizerResult recognizerResult, boolean b) {
            String str=JsonParser.parseIatResult(recognizerResult.getResultString());
            System.out.println("識別結果"+str);
        }

        @Override
        public void onError(SpeechError speechError) {
            System.out.println("識別出錯");
        }

        @Override
        public void onEvent(int i, int i1, int i2, Bundle bundle) {
            
        }
    };
4.添加監聽

mIat.startListening(recognizerListener);

官方Demo給出的解析Json的類:
package com.hxl.voicetest1;

import org.json.JSONArray;
import org.json.JSONObject;
import org.json.JSONTokener;

/**
 * Json結果解析類
 */
public class JsonParser {

    public static String parseIatResult(String json) {
        StringBuffer ret = new StringBuffer();
        try {
            JSONTokener tokener = new JSONTokener(json);
            JSONObject joResult = new JSONObject(tokener);

            JSONArray words = joResult.getJSONArray("ws");
            for (int i = 0; i < words.length(); i++) {
                // 轉寫結果詞,默認使用第一個結果
                JSONArray items = words.getJSONObject(i).getJSONArray("cw");
                JSONObject obj = items.getJSONObject(0);
                ret.append(obj.getString("w"));
                //如果需要多候選結果,解析數組其他字段
                //for(int j = 0; j < items.length(); j++)
                //{
                //JSONObject obj = items.getJSONObject(j);
                //ret.append(obj.getString("w"));
                //}
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return ret.toString();
    }

    public static String parseGrammarResult(String json) {
        StringBuffer ret = new StringBuffer();
        try {
            JSONTokener tokener = new JSONTokener(json);
            JSONObject joResult = new JSONObject(tokener);

            JSONArray words = joResult.getJSONArray("ws");
            for (int i = 0; i < words.length(); i++) {
                JSONArray items = words.getJSONObject(i).getJSONArray("cw");
                for (int j = 0; j < items.length(); j++) {
                    JSONObject obj = items.getJSONObject(j);
                    if (obj.getString("w").contains("nomatch")) {
                        ret.append("沒有匹配結果.");
                        return ret.toString();
                    }
                    ret.append("【結果】" + obj.getString("w"));
                    ret.append("【置信度】" + obj.getInt("sc"));
                    ret.append("n");
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
            ret.append("沒有匹配結果.");
        }
        return ret.toString();
    }
}

到這裏,科大訊飛語音最簡單的聽寫Demo就算完成了。這時你們會想:WTF?就這些我還不如自個兒看文檔.....

當然了,既然寫這篇博客,肯定不僅僅是簡單介紹科大訊飛語音聽寫的使用而已。

我想要的效果是:無限制時間錄音,並且我想知道我在第幾秒說了什麼話,回放錄音的時候,播放到哪就顯示相應的文字,還可以對識別錯誤的字段進行糾錯修改。

我們來看一下一些常用的設置參數(個人認爲),我們可以根據我們的需求來設置相應的參數
        // 清空參數
        mIat.setParameter(SpeechConstant.PARAMS, null);
        //短信和日常用語:iat (默認)  視頻:video  地圖:poi  音樂:music
        mIat.setParameter(SpeechConstant.DOMAIN, "iat");
        // 簡體中文:"zh_cn", 美式英文:"en_us"
        mIat.setParameter(SpeechConstant.LANGUAGE, "zh_cn");
        //普通話:mandarin(默認)
        //粵 語:cantonese
        //四川話:lmz
        //河南話:henanese<span style="font-family: Menlo;">     </span>
        mIat.setParameter(SpeechConstant.ACCENT, "mandarin ");
        // 設置聽寫引擎 "cloud", "local","mixed"  在線  本地  混合
        //本地的需要本地功能集成
        mIat.setParameter(SpeechConstant.ENGINE_TYPE, "cloud");
        // 設置返回結果格式 聽寫會話支持json和plain
        mIat.setParameter(SpeechConstant.RESULT_TYPE, "json");
        //設置是否帶標點符號 0表示不帶標點,1則表示帶標點。
        mIat.setParameter(SpeechConstant.ASR_PTT, "0");
        //只有設置這個屬性爲1時,VAD_BOS  VAD_EOS纔會生效,且RecognizerListener.onVolumeChanged纔有音量返回默認:1
        mIat.setParameter(SpeechConstant.VAD_ENABLE,"1");
        // 設置語音前端點:靜音超時時間,即用戶多長時間不說話則當做超時處理1000~10000
        mIat.setParameter(SpeechConstant.VAD_BOS, "5000");
        // 設置語音後端點:後端點靜音檢測時間,即用戶停止說話多長時間內即認爲不再輸入, 自動停止錄音0~10000
        mIat.setParameter(SpeechConstant.VAD_EOS, "1800");
        // 設置音頻保存路徑,保存音頻格式支持pcm、wav,設置路徑爲sd卡請注意WRITE_EXTERNAL_STORAGE權限
        // 注:AUDIO_FORMAT參數語記需要更新版本才能生效
        mIat.setParameter(SpeechConstant.AUDIO_FORMAT, "wav");
        //設置識別會話被中斷時(如當前會話未結束就開啓了新會話等),
        //是否通 過RecognizerListener.onError(com.iflytek.cloud.SpeechError)回調ErrorCode.ERROR_INTERRUPT錯誤。
        //默認false    [null,true,false]
        mIat.setParameter(SpeechConstant.ASR_INTERRUPT_ERROR,"false");
        //音頻採樣率  8000~16000  默認:16000
        mIat.setParameter(SpeechConstant.SAMPLE_RATE,"16000");
        //默認:麥克風(1)(MediaRecorder.AudioSource.MIC)
        //在寫音頻流方式(-1)下,應用層通過writeAudio函數送入音頻;
        //在傳文件路徑方式(-2)下,SDK通過應用層設置的ASR_SOURCE_PATH值, 直接讀取音頻文件。目前僅在SpeechRecognizer中支持。
        mIat.setParameter(SpeechConstant.AUDIO_SOURCE, "-1");
        //保存音頻文件的路徑   僅支持pcm和wav
        mIat.setParameter(SpeechConstant.ASR_SOURCE_PATH, Environment.getExternalStorageDirectory().getAbsolutePath() + "test.wav");


我首先想到當然就是設置音頻保存路徑了啊,這還不簡單
mIat.setParameter(SpeechConstant.AUDIO_FORMAT, "wav");
mIat.setParameter(SpeechConstant.ASR_SOURCE_PATH, "要保存的路徑");

時間戳這個好辦,根據錄音開始時記錄當前時間startTime,在RecognizerListener的onResult方法獲取當前時間currentTime,然後用currentTime-startTime 就是第幾秒說的話了(由於雲端識別會有延遲,這個秒數其實是不正確的,這裏先忽略這個問題)。

結束了?
不是,我們再來看一下科大訊飛的說明文檔中



科大訊飛對語音聽寫做了限制,端點超時最大也只能設置10秒,超過這個時間識別自動終止,不再對後續的語音部分進行識別。假如我們要長時間錄音,不可能讓用戶每10秒鐘就要說一句話,而且還有一個是值得我們注意的:



也就是說,就算我們連續不停的講話,音頻錄製最多也就是60秒而已,怎麼辦?
這時候我就想,我用自己的方法錄音,用科大訊飛的去識別,這是個好方法,我立馬在百度輸入框敲上 ”Android錄音
這裏推薦一個大神封裝的錄音類:http://www.cnblogs.com/Amandaliu/archive/2013/02/04/2891604.html


考慮到科大訊飛只支持wav、pcm文件,我只是把AudioRecordFunc類copy過來,考慮到科大訊飛對音頻文件識別的要求:

上傳音頻的採樣率與採樣精度:A:採樣率16KHZ或者8KHZ,單聲道,採樣精度16bit的PCM或者WAV格式的音頻

我將代碼進行了部分修改。
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

import android.media.AudioFormat;
import android.media.AudioRecord;

public class AudioRecordFunc {
    // 緩衝區字節大小  
    private int bufferSizeInBytes = 0;

    //AudioName裸音頻數據文件 ,麥克風
    private String AudioName = "";

    //NewAudioName可播放的音頻文件  
    private String NewAudioName = "";

    private AudioRecord audioRecord;
    private boolean isRecord = false;// 設置正在錄製的狀態  


    private static AudioRecordFunc mInstance;

    private AudioRecordFunc() {

    }

    public synchronized static AudioRecordFunc getInstance() {
        if (mInstance == null)
            mInstance = new AudioRecordFunc();
        return mInstance;
    }

    public int startRecordAndFile() {
        //判斷是否有外部存儲設備sdcard
        if (AudioFileFunc.isSdcardExit()) {
            if (isRecord) {
                return ErrorCode.E_STATE_RECODING;
            } else {
                if (audioRecord == null)
                    creatAudioRecord();

                audioRecord.startRecording();
                // 讓錄製狀態爲true  
                isRecord = true;
                // 開啓音頻文件寫入線程  
                new Thread(new AudioRecordThread()).start();

                return ErrorCode.SUCCESS;
            }

        } else {
            return ErrorCode.E_NOSDCARD;
        }

    }

    public void stopRecordAndFile() {
        close();
    }


    public long getRecordFileSize() {
        return AudioFileFunc.getFileSize(NewAudioName);
    }


    private void close() {
        if (audioRecord != null) {
            System.out.println("stopRecord");
            isRecord = false;//停止文件寫入  
            audioRecord.stop();
            audioRecord.release();//釋放資源  
            audioRecord = null;
        }
    }


    private void creatAudioRecord() {
        // 獲取音頻文件路徑
        AudioName = AudioFileFunc.getRawFilePath();
        NewAudioName = AudioFileFunc.getWavFilePath();

        // 獲得緩衝區字節大小  
        bufferSizeInBytes = AudioRecord.getMinBufferSize(AudioFileFunc.AUDIO_SAMPLE_RATE,
                AudioFormat.CHANNEL_IN_STEREO, AudioFormat.ENCODING_PCM_16BIT);


        // 創建AudioRecord對象(修改處)
        audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, 16000, AudioFormat.CHANNEL_ IN_MONO, AudioFormat.ENCODING_PCM_16BIT, bufferSizeInBytes);
    }

    class AudioRecordThread implements Runnable {
        @Override
        public void run() {
            writeDateTOFile();//往文件中寫入裸數據
            copyWaveFile(AudioName, NewAudioName);//給裸數據加上頭文件
        }
    }

    /**
     * 這裏將數據寫入文件,但是並不能播放,因爲AudioRecord獲得的音頻是原始的裸音頻
     * 如果需要播放就必須加入一些格式或者編碼的頭信息。但是這樣的好處就是你可以對音頻的 裸數據進行處理
     * 比如你要做一個愛說話的TOM貓在這裏就進行音頻的處理,然後重新封裝 所以說這樣得到的音頻比較容易做一些音頻的處理。
     */
    private void writeDateTOFile() {
        // new一個byte數組用來存一些字節數據,大小爲緩衝區大小
        byte[] audiodata = new byte[bufferSizeInBytes];
        FileOutputStream fos = null;
        int readsize = 0;
        try {
            File file = new File(AudioName);
            if (file.exists()) {
                file.delete();
            }
            fos = new FileOutputStream(file);
            // 建立一個可存取字節的文件
        } catch (Exception e) {
            e.printStackTrace();
        }
        while (isRecord == true) {
            readsize = audioRecord.read(audiodata, 0, bufferSizeInBytes);
            if (AudioRecord.ERROR_INVALID_OPERATION != readsize && fos != null) {
                try {
                    fos.write(audiodata);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        try {
            if (fos != null)
                fos.close();// 關閉寫入流
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    // 這裏得到可播放的音頻文件
    private void copyWaveFile(String inFilename, String outFilename) {
        FileInputStream in = null;
        FileOutputStream out = null;
        long totalAudioLen = 0;
        long totalDataLen = totalAudioLen + 36;
        long longSampleRate = AudioFileFunc.AUDIO_SAMPLE_RATE;
        int channels = 2;
        long byteRate = 16 * AudioFileFunc.AUDIO_SAMPLE_RATE * channels / 8;
        byte[] data = new byte[bufferSizeInBytes];
        try {
            in = new FileInputStream(inFilename);
            out = new FileOutputStream(outFilename);
            totalAudioLen = in.getChannel().size();
            totalDataLen = totalAudioLen + 36;
            WriteWaveFileHeader(out, totalAudioLen, totalDataLen, longSampleRate, channels, byteRate);
            while (in.read(data) != -1) {
                out.write(data);
            }
            in.close();
            out.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 這裏提供一個頭信息。插入這些信息就可以得到可以播放的文件。      
     * 爲我爲啥插入這44個字節,這個還真沒深入研究,不過你隨便打開一個wav
     * 音頻的文件,可以發現前面的頭文件可以說基本一樣哦。每種格式的文件都有自己特有的頭文件。
     */
    private void WriteWaveFileHeader(FileOutputStream out, long totalAudioLen, long totalDataLen, long longSampleRate, int channels, long byteRate) throws IOException {
        byte[] header = new byte[44];
        header[0] = 'R'; // RIFF/WAVE header
        header[1] = 'I';
        header[2] = 'F';
        header[3] = 'F';
        header[4] = (byte) (totalDataLen & 0xff);
        header[5] = (byte) ((totalDataLen >> 8) & 0xff);
        header[6] = (byte) ((totalDataLen >> 16) & 0xff);
        header[7] = (byte) ((totalDataLen >> 24) & 0xff);
        header[8] = 'W';
        header[9] = 'A';
        header[10] = 'V';
        header[11] = 'E';
        header[12] = 'f'; // 'fmt ' chunk
        header[13] = 'm';
        header[14] = 't';
        header[15] = ' ';
        header[16] = 16; // 4 bytes: size of 'fmt ' chunk
        header[17] = 0;
        header[18] = 0;
        header[19] = 0;
        header[20] = 1; // format = 1
        header[21] = 0;
        header[22] = (byte) channels;
        header[23] = 0;
        header[24] = (byte) (longSampleRate & 0xff);
        header[25] = (byte) ((longSampleRate >> 8) & 0xff);
        header[26] = (byte) ((longSampleRate >> 16) & 0xff);
        header[27] = (byte) ((longSampleRate >> 24) & 0xff);
        header[28] = (byte) (byteRate & 0xff);
        header[29] = (byte) ((byteRate >> 8) & 0xff);
        header[30] = (byte) ((byteRate >> 16) & 0xff);
        header[31] = (byte) ((byteRate >> 24) & 0xff);
        header[32] = (byte) (2 * 16 / 8); // block align
        header[33] = 0;
        header[34] = 16; // bits per sample
        header[35] = 0;
        header[36] = 'd';
        header[37] = 'a';
        header[38] = 't';
        header[39] = 'a';
        header[40] = (byte) (totalAudioLen & 0xff);
        header[41] = (byte) ((totalAudioLen >> 8) & 0xff);
        header[42] = (byte) ((totalAudioLen >> 16) & 0xff);
        header[43] = (byte) ((totalAudioLen >> 24) & 0xff);
        out.write(header, 0, 44);
    }
}

然後在錄音按鈕裏的點擊事件裏寫了這麼兩行行代碼:
                //開始進行語音聽寫
                mIat.startListening(mRecoListener);
                //開始錄音並且保存錄音到sd卡
                audioRecordFunc.startRecordAndFile();
這裏會發生一個很奇怪的現象,能聽寫的時候不能錄音,能錄音的時候不能聽寫,錄音的兩個類MediaRecorder、AudioRecord中MediaRecorder的start()方法中有這麼一句話:
The apps should  not start another recording session during recording.
(一個app不應該在錄音期間開啓另外一個錄音),原因我大致理解爲麥克風被佔用了


既然不能一邊錄音一邊識別,那我們只好錄完音後再上傳去識別,嗯,回頭看了一下設置的參數
        mIat.setParameter(SpeechConstant.AUDIO_SOURCE, "-2");
        mIat.setParameter(SpeechConstant.ASR_SOURCE_PATH, ”要識別的音頻絕對路徑“);
設置好參數之後
mIat.startListening(mRecoListener);
這下可以了,保存的音頻文件可以識別,而且識別的速度和講話的時候是一樣的,也就是說,你在第5秒說的話,就是在第五秒開始識別(不考慮網絡延遲的時候),這樣,就可以實現了我們的需求。
然而,事情往往沒有那麼簡單,還是回到了原始的問題,也就是前後端點超時或者一分鐘過後的音頻不再識別!!!

有兩個解決方法:
1、自己重新寫一個類繼承科大訊飛裏面的類,重寫裏面的方法
2、通過流的方式去控制,假如文件流讀取沒完成,音頻不再識別,那就重新激活,直到文件流讀取完畢
  兩秒鐘之後我立馬放棄了第一種解決方案,源碼是長這樣子的:
那就只能採用第二種方案了,此時我們要進行相應的參數設置:
mIat.setParameter(SpeechConstant.AUDIO_SOURCE, "-1");
//開始進行語音聽寫
mIat.startListening(mRecoListener);
//然後在進行音頻流輸入
mIat.writeAudio(bytes, 0, bytes.length);

錄完音後,根據音頻文件讀取流。代碼我就不粘貼出來了,因爲這個方法行不通,原因是因爲流讀取的速度太快,幾分鐘的文件一下子就讀取完了,端點超時問題還是會出現(ps:希望有個人告訴流能否控制其讀取速度,怎麼控制),而且就算能控制速度,假如用戶錄音一個小時的,你不可能又要花一個小時去識別音頻吧,這不現實!!

未完待續...





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