天天看點

Android TTS實作簡單閱讀器(一)

 <b>Android TTS</b><b>實作簡單閱讀器</b>

         簡單的Txt文本閱讀器,主要用于介紹Google Android的TTS接口。

<b>一、</b><b>TTS</b>

         在package android.speech.tts内,主要閱讀下TextToSpeech.OnInitListener、TextToSpeech. OnUtteranceCompletedListener兩個接口和TextToSpeech、TextToSpeech.Engine兩個類。

         具體還是自己去看下SDK文檔吧。(我也是完整閱讀過了的^^)

<b>二、</b><b>TTS</b><b>引擎</b>

         而Android的SDK中還提供了TTS服務的接口,用于供應商提供服務的。也就是語音合成服務商隻管提供它的服務,開發者隻管使用Android的TTS接口,使用者自己安裝想要的服務自己進行選擇。

<b>1</b><b>)關于訊飛(貌似廣告?)</b>

         好吧,少說點了,它也提供了個開發者平台。如下:

         有試了下它那語音分析,話說,彈出的框框能不能好看點啊。(做個小話筒就好了麼T^T)

         恩,還有,現在訊飛是要開始宣傳了麼?貌似3月22日什麼開發者大會-_-!(又廣告了?)

<b>2</b><b>)其他中文引擎</b>

         參見文章:Android中文語音合成(TTS)各家引擎對比。(原網址打不開==,另外的網址就不貼了,搜下吧)

<b>三、閱讀器工程</b>

         現在學乖了,直接貼些代碼得了==。代碼中注釋應該滿清晰詳細了^^。

<b>1</b><b>)界面布局</b>

         布局由main.xml includes header.xml &amp; footer.xml組成,并寫有了定時收起等。

TtsFatherActivity.java

/** 

 * 1)本類用于main.xml的布局控制。子類再去實作各控件的TTS相關功能。 

 * 2)用繼承方式實作是為了利用布局中控件的onClick屬性(懶得多寫代碼==!)。 

 */ 

public abstract class TtsFatherActivity extends Activity { 

    private GestureDetector gd; // 手勢檢測 

    private GlobalUtil globalUtil; // 全局公用類 

    private ScrollView scrollView; // 滾動視圖 

    private LinearLayout headerLayout, footerLayout; // 頂部、底部布局 

    private TextView textView; // 文本标簽 

    private static final long ANIM_DURATION = 500; // 動畫時間(毫秒) 

    private static final int DIALOG_TEXT_LIST = 0; // 文本清單對話框id 

    private final String[] textPaths = new String[] { "one.txt", "two.txt", 

            "浏覽..." }; // assets内文本資源路徑 

    protected String textTitle; // 文本标題 

    protected String textContent; // 文本内容 

    private Timer timer; // 計時器 

    private static final long TIMEOUT = 2000; // 逾時時間 

    private static final int TIMER_LAYOUT_OUT = 1; // 布局收起 

    private boolean isLayoutOut = false; // 布局收起狀态 

    /** Handler處理操作 */ 

    public Handler mHandler = new Handler() { 

        @Override 

        public void handleMessage(Message msg) { 

            switch (msg.what) { 

            case TIMER_LAYOUT_OUT: 

                /* headerLayout收起動畫 */ 

                globalUtil.startTransAnim(headerLayout, 

                        GlobalUtil.AnimMode.UP_OUT, ANIM_DURATION); 

                headerLayout.setVisibility(View.GONE); 

                /* footerLayout收起動畫 */ 

                globalUtil.startTransAnim(footerLayout, 

                        GlobalUtil.AnimMode.DOWN_OUT, ANIM_DURATION); 

                footerLayout.setVisibility(View.GONE); 

                isLayoutOut = true; // 重置布局收起狀态 

                break; 

            } 

        } 

    }; 

    @Override 

    public void onCreate(Bundle savedInstanceState) { 

        super.onCreate(savedInstanceState); 

        setContentView(R.layout.main); 

        gd = new GestureDetector(new MySimpleGesture()); // 手勢檢測處理 

        globalUtil = GlobalUtil.getInstance(); // 擷取全局公用類 

        scrollView = (ScrollView) findViewById(R.id.scrollView); // 擷取滾動視圖 

        headerLayout = (LinearLayout) findViewById(R.id.headerLayout); // 擷取頂部布局 

        footerLayout = (LinearLayout) findViewById(R.id.footerLayout); // 擷取底部布局 

        textView = (TextView) findViewById(R.id.textView); 

        setText(0); // 預設顯示“上邪.txt” 

        newTimerLayoutOut(); // 定時收起布局 

    } 

    /** 使用GestureDetector檢測手勢(ScrollView内也需監聽時的方式) */ 

    public boolean dispatchTouchEvent(MotionEvent ev) { 

        gd.onTouchEvent(ev); 

        scrollView.onTouchEvent(ev); 

        return super.dispatchTouchEvent(ev); 

    /** onCreateDialog */ 

    protected Dialog onCreateDialog(int id) { 

        switch (id) { 

        case DIALOG_TEXT_LIST: 

            return new AlertDialog.Builder(this).setItems(textPaths, 

                    new DialogInterface.OnClickListener() { 

                        @Override 

                        public void onClick(DialogInterface dialog, int which) { 

                            if (2 == which) { 

                                // 跳轉到檔案浏覽Activity 

                                startActivityForResult(new Intent( 

                                        TtsFatherActivity.this, 

                                        FileBrowserActivity.class), 

                                        FileBrowserActivity.CODE_FILE_BROWSER); 

                            } else { 

                                setText(which); // 設定文本内容 

                            } 

                        } 

                    }).create(); 

        return super.onCreateDialog(id); 

    protected void onActivityResult(int requestCode, int resultCode, Intent data) { 

        if (requestCode == FileBrowserActivity.CODE_FILE_BROWSER) { 

            if (resultCode == RESULT_OK) { 

                // 獲得檔案名稱 

                String filename = data.getExtras().getString( 

                        FileBrowserActivity.KEY_FILENAME); 

                this.textTitle = filename; 

                try { 

                    // FileInputStream fis = new FileInputStream( 

                    // new File(filename)); 

                    // // FileInputStream不支援mark/reset操作,不該直接這樣 

                    // String encoding = globalUtil.getIsEncoding(fis); 

                    // textContent = globalUtil.is2Str(fis, encoding); 

                    /** 

                     * TXT簡單判斷編碼類型後轉字元串 

                     *  

                     * ps: 

                     * 1)扯淡,3.58MB的txt讀出來了== 

                     *    看來需要轉成BufferedReader以readLine()方式讀好些啊 

                     *     

                     * 2)TextView将大文本全部顯示,這貌似... 

                     *       時間主要花費在文本顯示過程,不改進了,暫時将就吧== 

                     *    2.1)用View自定義個控件顯示文本也蠻久的,未減少多少時間。 

                     *    2.2)至于AsyncTask,文本顯示還是要在UI線程的==。 

                     *    如果我們要仿個閱讀器,用View自定義個控件還是必須的。 

                     *    1)分段讀取大文本,可以考慮3段(前後兩段用于緩沖) 

                     *       根據滑屏&amp;顯示内容等,注意文本顯示銜接。 

                     *    2)滾動條可以外面套個ScrollView。由各屬性判斷出大文本需要顯示的高度, 

                     *       重寫onMeasure用setMeasuredDimension()設定好,才會有滾動條。 

                     *       當然自己用scrollTo()、scrollBy()實作動畫也是好的。 

                     *    3)至于其他選中目前行啊什麼的,慢慢寫就成了... 

                     *    不知道大家還有什麼好的想法沒? 

                     */ 

                    // long time1 = System.currentTimeMillis(); 

                    textContent = globalUtil.is2Str(new FileInputStream( 

                            new File(filename))); 

                    // long time2 = System.currentTimeMillis(); 

                    // Log.e("TAG1", "==" + (time2 - time1) + "=="); 

                    textView.setText(textContent); 

                    // long time3 = System.currentTimeMillis(); 

                    // Log.e("TAG1", "==" + (time3 - time2) + "=="); 

                } catch (Exception e) { 

                    textView.setText(R.string.text_error); 

                    textContent = ""; 

                } 

    /** 設定文本内容 */ 

    private void setText(int textIndex) { 

        this.textTitle = textPaths[textIndex]; 

        try { 

            textContent = globalUtil.is2Str(getAssets().open(textTitle), 

                    "UTF-8"); 

            textView.setText(textContent); 

        } catch (IOException e) { 

            textView.setText(R.string.text_error); 

            textContent = ""; 

    /** 定時收起布局(已定時時重新開始定時) */ 

    protected void newTimerLayoutOut() { 

        if (null != timer) { 

            timer.cancel(); 

        timer = new Timer(); 

        // 逾時TIMEOUT退出 

        timer.schedule(new TimerTask() { 

            @Override 

            public void run() { 

                mHandler.sendEmptyMessage(TIMER_LAYOUT_OUT); 

        }, TIMEOUT); 

    /** 自定義手勢類 */ 

    private class MySimpleGesture extends SimpleOnGestureListener { 

        /** 輕按兩下第二下 */ 

        public boolean onDoubleTap(MotionEvent e) { 

            if (isLayoutOut) { 

                /* headerLayout進入動畫 */ 

                headerLayout.setVisibility(View.VISIBLE); 

                        GlobalUtil.AnimMode.UP_IN, ANIM_DURATION); 

                /* footerLayout進入動畫 */ 

                footerLayout.setVisibility(View.VISIBLE); 

                        GlobalUtil.AnimMode.DOWN_IN, ANIM_DURATION); 

                newTimerLayoutOut(); // 定時收起布局 

                isLayoutOut = false; // 重置布局收起狀态 

            } else { 

                /* headerLayout退出動畫 */ 

                /* footerLayout退出動畫 */ 

                // 取消定時收起動畫 

                if (null != timer) { 

                    timer.cancel(); 

            return false; 

        /** 長按螢幕時 */ 

        public void onLongPress(MotionEvent e) { 

            // 顯示文本清單對話框 

            showDialog(DIALOG_TEXT_LIST); 

<b>2</b><b>)TTS</b><b>控制</b>

         音量&amp;語速控制也寫了的^^。

TtsSampleActivity.java<b></b>

public class TtsSampleActivity extends TtsFatherActivity implements 

        OnSeekBarChangeListener, TextToSpeech.OnInitListener, 

        TextToSpeech.OnUtteranceCompletedListener { 

    // private static final String TAG = "TtsSampleActivity"; // 日志标記 

    private AudioManager audioManager; // 音頻管理對象 

    // TTS音量類型(AudioManager.STREAM_MUSIC = AudioManager.STREAM_TTS = 11) 

    private static final int STREAM_TTS = AudioManager.STREAM_MUSIC; 

    private TextToSpeech mTts; // TTS對象 

    private static final int REQ_CHECK_TTS_DATA = 110; // TTS資料校驗請求值 

    private boolean isSetting = false; // 進入設定标記 

    private boolean isRateChanged = false; // 速率改變标記 

    private boolean isStopped = false; // TTS引擎停止發聲标記 

    private float mSpeechRate = 1.0f; // 朗讀速率 

    private SeekBar volumeBar, speedBar; // 音量&amp;語速 

    // 合成聲音資源檔案的路徑 

    private static final String SAVE_DIR_PATH = "/sdcard/AndroidTTS/"; 

    private static final String SAVE_FILE_PATH = SAVE_DIR_PATH + "sound.wav"; 

        // 獲得音頻管理對象 

        audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); 

        /* volumeBar */ 

        volumeBar = (SeekBar) findViewById(R.id.volumeBar); 

        volumeBar.setOnSeekBarChangeListener(this); 

        // 由目前音量設定進度(需保證進度上限=音頻上限=15,否則按比例設定) 

        volumeBar.setProgress(audioManager.getStreamVolume(STREAM_TTS)); 

        /* speedBar */ 

        speedBar = (SeekBar) findViewById(R.id.speedBar); 

        speedBar.setOnSeekBarChangeListener(this); 

        initDirs(SAVE_DIR_PATH); // 初始化檔案夾路徑 

    /** saveFileBtn點選事件 */ 

    public void saveFile(View v) { 

        // 将文本合成聲音資源檔案 

        int resId = TextToSpeech.SUCCESS == ttsSaveFile(textContent, 

                SAVE_FILE_PATH) ? R.string.synt_success : R.string.synt_fail; 

        Toast.makeText(this, resId, Toast.LENGTH_SHORT).show(); // Toast提示 

        newTimerLayoutOut(); // 重新定時收起布局 

    /** playFileBtn點選事件 */ 

    public void playFile(View v) { 

        ttsPlayFile(SAVE_FILE_PATH); // 播放指定的使用檔案 

    /** stopBtn點選事件 */ 

    public void stop(View v) { 

        ttsStop(); // 停止目前發聲 

    /** playBtn點選事件 */ 

    public void play(View v) { 

        ttsPlay(); // tts合成語音播放 

    /** settingBtn點選事件 */ 

    public void setting(View v) { 

        // 跳轉到“語音輸入與輸出”設定界面&amp;設定标志位 

        isSetting = toTtsSettings(); 

    /** SeekBar進度改變時 */ 

    public void onProgressChanged(SeekBar seekBar, int progress, 

            boolean fromUser) { 

        switch (seekBar.getId()) { 

        case R.id.volumeBar: 

            // 由設定目前TTS音量(需保證進度上限=音頻上限=15,否則按比例設定) 

            audioManager.setStreamVolume(STREAM_TTS, progress, 0); 

            break; 

        case R.id.speedBar: 

            /* 需要重新綁定TTS引擎,速度在onInit()裡設定 */ 

            isRateChanged = true; // 速率改變标記 

            // 最大值為20時,以下方式計算為0.5~2倍速 

            mSpeechRate = (progress &gt;= 10) ? (progress / 10f) 

                    : (0.5f + progress / 20f); 

            // 校驗TTS引擎安裝及資源狀态,重新綁定引擎 

            checkTtsData(); 

    /** SeekBar開始拖動時 */ 

    public void onStartTrackingTouch(SeekBar seekBar) { 

    /** SeekBar結束拖動時 */ 

    public void onStopTrackingTouch(SeekBar seekBar) { 

    /** 

     * TTS引擎初始化時回調方法 

     *  

     * 引擎相關參數(音量、語速)等都需在這設定。 

     * 1)建立完成後再去設定,會有意外的效果^^ 

     * 2)音量也可由AudioManager進行控制(和音樂一個媒體流類型) 

     */ 

    public void onInit(int status) { 

        if (status == TextToSpeech.SUCCESS) { 

            mTts.setSpeechRate(mSpeechRate); // 設定朗讀速率 

            // 設定發聲合成監聽,注意也需要在onInit()中做才有效 

            mTts.setOnUtteranceCompletedListener(this); 

            if (isRateChanged) { 

                ttsPlay(); // tts合成語音播放 

                isRateChanged = false; // 重置标記位 

    /**  

     * TTS引擎完成發聲完成時回調方法 

     * 1)stop()取消時也會回調 

     * 2)需在onInit()内設定接口 

     * 3)utteranceId由speak()時的請求參數設定 

     * 參數key:TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID 

    public void onUtteranceCompleted(final String utteranceId) { 

        /* 測試該接口的Toast提示 */ 

        runOnUiThread(new Runnable() { 

                int resId = isStopped ? R.string.utte_stopped 

                        : R.string.utte_completed; 

                // 提示文本發生完成 

                Toast.makeText(getApplicationContext(), 

                        getString(resId, utteranceId), Toast.LENGTH_SHORT) 

                        .show(); 

        }); 

    /** onActivityResult */ 

        if (requestCode == REQ_CHECK_TTS_DATA) { 

            switch (resultCode) { 

            case TextToSpeech.Engine.CHECK_VOICE_DATA_PASS: // TTS引擎可用 

                // 針對于重新綁定引擎,需要先shutdown() 

                if (null != mTts) { 

                    ttsStop(); // 停止目前發聲 

                    ttsShutDown(); // 釋放資源 

                mTts = new TextToSpeech(this, this); // 建立TextToSpeech對象 

            case TextToSpeech.Engine.CHECK_VOICE_DATA_BAD_DATA: // 資料錯誤 

            case TextToSpeech.Engine.CHECK_VOICE_DATA_MISSING_DATA: // 缺失資料資源 

            case TextToSpeech.Engine.CHECK_VOICE_DATA_MISSING_VOLUME: // 缺少資料存儲量 

                notifyReinstallDialog(); // 提示使用者是否重裝TTS引擎資料的對話框 

            case TextToSpeech.Engine.CHECK_VOICE_DATA_FAIL: // 檢查失敗 

            default: 

        super.onActivityResult(requestCode, resultCode, data); 

    /** 校驗TTS引擎安裝及資源狀态 */ 

    private boolean checkTtsData() { 

            Intent checkIntent = new Intent(); 

            checkIntent.setAction(TextToSpeech.Engine.ACTION_CHECK_TTS_DATA); 

            startActivityForResult(checkIntent, REQ_CHECK_TTS_DATA); 

            return true; 

        } catch (ActivityNotFoundException e) { 

    /** 提示使用者是否重裝TTS引擎資料的對話框 */ 

    private void notifyReinstallDialog() { 

        new AlertDialog.Builder(this).setTitle("TTS引擎資料錯誤") 

                .setMessage("是否嘗試重裝TTS引擎資料到裝置上?") 

                .setPositiveButton("是", new DialogInterface.OnClickListener() { 

                    @Override 

                    public void onClick(DialogInterface dialog, int which) { 

                        // 觸發引擎在TTS引擎在裝置上安裝資源檔案 

                        Intent dataIntent = new Intent(); 

                        dataIntent 

                                .setAction(TextToSpeech.Engine.ACTION_INSTALL_TTS_DATA); 

                        startActivity(dataIntent); 

                    } 

                }).setNegativeButton("否", null).show(); 

    /** 跳轉到“語音輸入與輸出”設定界面 */ 

    private boolean toTtsSettings() { 

            startActivity(new Intent("com.android.settings.TTS_SETTINGS")); 

    protected void onStart() { 

        checkTtsData(); // 校驗TTS引擎安裝及資源狀态 

        super.onStart(); 

    protected void onResume() { 

        /* 從設定傳回後重新綁定TTS,避免仍用舊引擎 */ 

        if (isSetting) { 

            checkTtsData(); // 校驗TTS引擎安裝及資源狀态 

            isSetting = false; 

        super.onResume(); 

    protected void onStop() { 

        /* HOME鍵 */ 

        super.onStop(); 

    public void onBackPressed() { 

        /* BACK鍵 */ 

        ttsShutDown(); // 釋放資源 

        super.onBackPressed(); 

    /** tts合成語音播放 */ 

    private int ttsPlay() { 

        if (null != mTts) { 

            isStopped = false; // 設定标記 

            /** 

             * 叙述text。 

             *  

             * 1) 參數2(int queueMode) 

             *    1.1)QUEUE_ADD:增加模式。增加在隊列尾,繼續原來的說話。 

             *    1.2)QUEUE_FLUSH:重新整理模式。中斷正在進行的說話,說新的内容。 

             * 2)參數3(HashMap&lt;String, String&gt; params) 

             *    2.1)請求的參數,可以為null。 

             *    2.2)注意KEY_PARAM_UTTERANCE_ID。 

             */ 

            HashMap&lt;String, String&gt; params = new HashMap&lt;String, String&gt;(); 

            params.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, textTitle); 

            return mTts.speak(textContent, TextToSpeech.QUEUE_FLUSH, params); 

        return TextToSpeech.ERROR; 

    // /** 判斷TTS是否正在發聲 */ 

    // private boolean isSpeaking() { 

    // // 使用mTts.isSpeaking()判斷時,第一次speak()傳回true,多次就傳回false了。 

    // return audioManager.isMusicActive(); 

    // } 

    /** 停止目前發聲,同時放棄所有在等待隊列的發聲 */ 

    private int ttsStop() { 

        isStopped = true; // 設定标記 

        return (null == mTts) ? TextToSpeech.ERROR : mTts.stop(); 

    /** 釋放資源(解除語音服務綁定) */ 

    private void ttsShutDown() { 

            mTts.shutdown(); 

    /** 初始化檔案夾路徑 */ 

    private void initDirs(final String dirpath) { 

        File file = new File(dirpath); 

        if (!file.exists()) { 

            file.mkdirs(); 

    /** 将文本合成聲音資源檔案 */ 

    private int ttsSaveFile(String text, final String filename) { 

        return (null == mTts) ? TextToSpeech.ERROR : mTts.synthesizeToFile( 

                text, null, filename); 

    /** 播放指定的使用檔案 */ 

    private int ttsPlayFile(final String filename) { 

        // 如果存在FILENAME_SAVE檔案的話 

        if (new File(filename).exists()) { 

            try { 

                /* 使用MediaPlayer進行播放(沒進行控制==) */ 

                MediaPlayer player = new MediaPlayer(); 

                player.setDataSource(filename); 

                player.prepare(); 

                player.start(); 

                return TextToSpeech.SUCCESS; 

            } catch (Exception e) { 

                e.printStackTrace(); 

                return TextToSpeech.ERROR; 

     本文轉自winorlose2000 51CTO部落格,原文連結:http://blog.51cto.com/vaero/809873,如需轉載請自行聯系原作者

繼續閱讀