2015年3月17日 星期二

[Android] Google語音辨識 - 關掉Beep聲

在前二篇裡,我們用了不同的方法來實現語音辨識,
[Android] Google語音辨識 - 使用RecognizerIntent
[Android] Google語音辨識 - 自己實作UI
不過,怎麼語音輸入一開始都會有一個Beep聲啊?
個人覺得那是個提示音,沒什麼不好,
但,如果你覺得吵,也可以把它關掉, 底下二種方法都可以:
AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
am.setStreamSolo(AudioManager.STREAM_VOICE_CALL, true);
sr.startListening(recognizerIntent);
或者
AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
am.setStreamMute(AudioManager.STREAM_SYSTEM, true);
sr.startListening(recognizerIntent);

[Android] Google語音辨識 - 自己實作UI

在[Android] Google語音辨識 - 使用RecognizerIntent裡,
我們使用了RecognizerIntent來執行語音辨識,
但是,如果我們想要有自己特色的UI呢?

我們還另外需要SpeechRecognizer的幫忙
Intent recognizerIntent;
private SpeechRecognizer sr;

sr = SpeechRecognizer.createSpeechRecognizer(this);       
sr.setRecognitionListener(new listener());        
recognizerIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);        
recognizerIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL,RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);
recognizerIntent.putExtra(RecognizerIntent.EXTRA_MAX_RESULTS,5);//5是你需要回傳多少個結果 

//開始辨識,將此code移到某個按鈕的onClick()裡
sr.startListening(recognizerIntent);  

//停止辨識,將此code移到某個按鈕的onClick()裡
//我們平常不需要做這個處理,語音辨識完畢它自己會停
//但如果我們想中斷它,或者有異常發生時,就可以呼叫底下的程式碼
sr.stopListening();
sr.cancel();
上面的listener(),代碼如下:
private static final int RECOGNIZER_ERROR_NETWORK_TIMEOUT = 1;//網路超時
private static final int RECOGNIZER_ERROR_NETWORK = 2;//其它的網路相關錯誤
private static final int RECOGNIZER_ERROR_AUDIO = 3;//無法錄音
private static final int RECOGNIZER_ERROR_SERVER = 4;//伺服器異常
private static final int RECOGNIZER_ERROR_CLIENT = 5;//其它手機端的異常
private static final int RECOGNIZER_ERROR_SPEECH_TIMEOUT = 6;//沒有語音輸入
private static final int RECOGNIZER_ERROR_NO_MATCH = 7;//沒有符合的辨識結果
private static final int RECOGNIZER_ERROR_RECOGNIZER_BUSY = 8;//RecognitionService 忙碌中
private static final int RECOGNIZER_ERROR_INSUFFICIENT_PERMISSIONS = 9;//權限不足

class listener implements RecognitionListener          
{
    public void onReadyForSpeech(Bundle params)
    { 
      //已經準備好可以接受語音輸入,可以在此時通知UI秀出相關icon,告知使用者開始說話             
    }
    public void onBeginningOfSpeech()
    {  
      //使用者開始說話,可以在此時通知UI秀出相關icon,告知使用者已收到聲音輸入了        
    }
    public void onRmsChanged(float rmsdB)
    { 
      //聲音輸入的強度變化,值是0~10
      //可以在此時通知UI秀出聲音大小的相關icon
      //onRmsChanged()來的非常頻繁和快速,最好是判斷有變化才通知UI更新
    }
    public void onBufferReceived(byte[] buffer)
    {       
    }
    public void onEndOfSpeech()
    {
       //使用者結束說話,系統會開始進行聲音辨識
    }
    public void onError(int error)
    {
       switch(error)
       {
        case RECOGNIZER_ERROR_NETWORK_TIMEOUT:         
         break;
        case RECOGNIZER_ERROR_NETWORK:         
         break;
        case RECOGNIZER_ERROR_AUDIO:         
         break;
        case RECOGNIZER_ERROR_SERVER:         
         break;
        case RECOGNIZER_ERROR_CLIENT:         
         break;
        case RECOGNIZER_ERROR_SPEECH_TIMEOUT:         
         break;
        case RECOGNIZER_ERROR_NO_MATCH:         
         break;
        case RECOGNIZER_ERROR_RECOGNIZER_BUSY:         
         break;
        case RECOGNIZER_ERROR_INSUFFICIENT_PERMISSIONS:         
         break;
       }
    }
    public void onResults(Bundle results)                   
    {
        //辨識的結果
        ArrayList data = results.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION);         
        String firstMatched = (String) data.get(0);         
        txtResult.setText(firstMatched);
    }
    public void onPartialResults(Bundle partialResults)
    {
    }
    public void onEvent(int eventType, Bundle params)
    {
    }
}

[Android] Google語音辨識 - 使用RecognizerIntent

Google的語音辨識,
不管是中文或英文,
基本上準確度都還蠻高,
如果程式中需要語音辨識的功能,
方法如下:
private static final int RECOGNITION_REQUEST_CODE = 1234;//當成溝通的tag而且,數字隨便定

Intent it = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
it.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);
//it.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_WEB_SEARCH);
it.putExtra(RecognizerIntent.EXTRA_PROMPT, "請說...");
this.startActivityForResult(it, RECOGNITION_REQUEST_CODE);
上面的範例是直接呼叫RecognizerIntent,
所以會跳出Android已經定義好的UI,
它會自己處理收音,然後辨識,
 EXTRA_LANGUAGE_MODEL傳入的參數可以是RecognizerIntent.LANGUAGE_MODEL_FREE_FORM或RecognizerIntent.LANGUAGE_MODEL_WEB_SEARCH,
差別是什麼?
LANGUAGE_MODEL_FREE_FORM:通常我們會用這個參數,不管句子長短, LANGUAGE_MODEL_WEB_SEARCH:因為是網頁搜尋資料為base,通常是短句,對於中文的辨識程也較好,
不過,經測試結果,好像結果差異不大,呵...

 辨識好的結果呢?
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);    
    String firstMatched;  

    if(requestCode == RECOGNITION_REQUEST_CODE && resultCode == RESULT_OK){
        //辨識結果不只一個,會從最接近的開始往後排序
        ArrayList<string> resultList = data.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS);
        firstMatched = (String)resultList.get(0);//通常我們只取第一個來用
    } else {
        firstMatched = "無法辨識";
    }
    
    result.setText(firstMatched);
}
整個辨識過程,要確定網路有連上線,
假如你想用離線的方式,
你可以到"設定"裡,有一個"下載離線語音辨識資料",
將想要的語言檔案下載回來,
如果網路沒開,還是可以進行語音辨識,
不過,實驗結果,辨識率就沒有那麼好了...

[Android] 為什麼我的thread停不了

通常thread的程式大概如下:
private class myThread extends Thread {
 @Override
 public void run() {
  super.run();
  Log.d(TAG,"myThread() entered...");
  while(!isInterrupted()) {
   Thread.sleep(1000);
    }
    catch (InterruptedException e) {   
     e.printStackTrace();    
    }
   }    
  }
  Log.d(TAG,"myThread() exited...");
 }
}
如果你想要停止這個thread,
我們可用interrupt()的方法,
例如,mMyThread = new myThread()是用來建立,
那mMyThread.interrupt()就是用來停止它,

可是我們發現Log.d(TAG,"myThread() entered...")會被執行,
表示mMyThread = new myThread()沒問題,
但是Log.d(TAG,"myThread() exited...")怎麼都沒被呼叫呢?
明明我們有呼叫mMyThread.interrupt()啊...

原來,thread可能在Thread.sleep()裡,
你呼叫interrupt()時,Thread會跑到catch (InterruptedException e),
但此時呢,interrupt會被reset,
所以回到while(!isInterrupted()),它還是成立,

那麼,我們該如何做呢,
private class myThread extends Thread {
 @Override
 public void run() {
  super.run();
  Log.d(TAG,"myThread() entered...");
  while(!isInterrupted()) {
   Thread.sleep(1000);
    }
    catch (InterruptedException e) {   
     e.printStackTrace(); 
     Thread.currentThread().interrupt();
    }
   }    
  }
  Log.d(TAG,"myThread() exited...");
 }
}
只要在"catch (InterruptedException e)"加上"Thread.currentThread().interrupt()"即可,
再設一次interrupt,
此時"Log.d(TAG,"myThread() exited...")"就會被執行到了

[Android] 如何複製檔案到system目錄

不管是"/system"或者"/data",預設都是read-only,
如果你在程式中想要將檔案複製過去,
需要先做一些處理,
程式範例如下:
Process process = Runtime.getRuntime().exec("su");
DataOutputStream output = new DataOutputStream(process.getOutputStream());
output.writeBytes("mount -o remount,rw -t yaffs2 /dev/block/mtdblock3 /system\n");
String sCatCommand = "cat "+Environment.getExternalStorageDirectory()+"/myTest/test.dat"+ "> /system/"+"test.dat"+"\n";
output.writeBytes(sCatCommand);
output.writeBytes("mount -o remount,ro -t yaffs2 /dev/block/mtdblock3 /system\n");
output.writeBytes("exit\n");  
output.flush();
process.waitFor();
上面的範例,是將SD卡裡的"/myTest/test.dat"拷貝到"/system"

[Android] 關於服務Service - 如何讓Service永遠不被Killed

如前一篇所說([Android] 關於服務Service - Service會被Killed?),
雖然Service被Killed後,通常系統會再重啓它,
但...萬一系統沒有這麼做呢?
我們是否有另外的方法可以保護自己的Service永生不滅?

有一個簡單方式,就是再建一個monitorService,
它的工作就是發現myService不見時,負責把myService再重啓,

如同[Android] 如何在開機後自動載入程式裡的做法,
有一個BootUpReceiverClass.java來接收開機時系統的訊息,
但其實它同時也可以接收Broadcast過來的訊息,
我們需要在AndroidManifest.xml增加一個intent-filter,
<receiver android:enabled="true" android:name="myTest.com.BootUpReceiverClass"
 android:permission="android.permission.RECEIVE_BOOT_COMPLETED">
 <intent-filter>
 <action android:name="android.intent.action.BOOT_COMPLETED" />
 <category android:name="android.intent.category.DEFAULT" />
 </intent-filter>
 <intent-filter>
 <action android:name="myTest.com.BootUpReceiverClass" /> 
 <category android:name="android.intent.category.DEFAULT" />       
 </intent-filter>
</receiver> 
接下來,就是monitorService部份,
假設我們在monitorService裡建了一個monitorThread,
它負責檢查"myTest.com.myServiceClass"有沒有在執行,
如果沒有,就sendBroadcast()給"myTest.com.BootUpReceiverClass"
private boolean isMyServiceRunning(String className) {
  try
  {
      ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
      for (RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {       
          if (className.equals(service.service.getClassName())) {
              return true;
          }
      }
  }catch (Exception e) {   
   e.printStackTrace();   
  }
     return false;
 }
 private class monitorThread extends Thread {
  @Override
  public void run() {
   super.run();   
   while(!isInterrupted()) {
    try {     
     if(!isMyServiceRunning("myTest.com.myServiceClass"))
     {      
      try
      {
       Intent intent = new Intent("myTest.com.BootUpReceiverClass");
       if(intent!=null)
       {        
           sendBroadcast(intent);
                 Thread.sleep(15000);
       }
      }catch (Exception e) {       
       e.printStackTrace();       
      }
     } 
     else
     {      
      Thread.sleep(3000);
     }     
    } catch (InterruptedException e) {     
     e.printStackTrace();     
    }
   }   
  }
 }
那..如果monitorService也被kill掉了呢?
所以,在myService裡也要一個類似的機制,
一直監視monitorService有沒有在執行,
如果不見了,就把它重啓,
程式碼是類似的,這邊就不貼了,

至於多久去檢查對方在不在呢?
經驗上,2~5秒應該都可以,
你可以去應用程式的執行中列表去看myService和monitorService是不是都在,
並且,試著關掉myService,看看會不會被重啓,
反過來,關掉monitorService,看看會不會被重啓...

那...如果monitorService和myService同時被killed呢?
人工去砍是很難成功的,
因為其中一個被關掉,立刻就會另一個重啓,
除非是系統才有辦法一次關二個,
這也表示,系統loading或記憶體真的無法負荷了,
就算myService仍然活著,也會卡卡的吧...

[Android] 關於服務Service - Service會被Killed?

是的,Service會被killed,尤其是Low Memory時,
如果系統loading不重時呢?還是有可能,
以下就Service相關的一些設定來討論一下...

1. onStartCommand()的回傳值
START_STICKY:之前的範例裡,我們就是採用這回傳值,
Service被killed後,在系統loading允許下,Service會被重新啓動,
不過,傳進來的intent可能是null,
如果我們需要從intent取得資料,要先做判斷:
@Override
 public int onStartCommand(Intent intent, int flags, int startId) {
  if(intent != null)
  {
   String sData1=Intent.getStringExtra("DATA_KEY1");
   int iData2=Intent.getIntExtra("DATA_KEY2",0);
   mServiceThread = new ServiceThread();
   mServiceThread.start();
   }
   else
   {
     Intent i = new Intent(xxx);
     sendBroadcast(i);
   }
    return  START_STICKY ; 
 }      
如果你就是需要這些資料才能開始Service,
那麼,你需要透過sendBroadcast(),
發一個broadcast event給當初啓動Service的Activity,
從那裡再啓動Service一次,順便把資料帶入,
經實驗結果,Service大概在30分鐘左右會被重啓(每個裝置會不同),
尤其是Service在idle時,
例如監控網路通訊,如果都沒有封包,
Service一直沒事做,那它可能就會被killed,然後很快又被重啓,
你可以看到執行中,Service不見了幾秒,
目前是沒遇到重啓失敗的情形
START_NOT_STICKY: Service被killed就結束了,不會自動重啓
START_REDELIVER_INTENT: Service被killed後會被重啓,
並且傳入最後的intent,
也就是說,intent不會是null,
不過,不知為何,我使用這個參數時程式會hang up

2. 修改AndroidManifest.xml
有人建議可以加上<intent-filter android:priority="1000"></intent-filter>來提高Service的Priority,
經實際結果,被重啓時間有稍微拉長些,大概60分鐘會重啓一次

其它還有人試著在onDestroy()裡重啓自己,
但因為Service被killed時,onDestroy()不見得會被呼叫,
所以此法我就沒有試了...

不管怎樣,怎麼好像都會被重啓呢?
有沒有不被重啓的方法?
可以試試把Service設成Foreground Service:
Notification notification = new Notification(R.drawable.launch, "Your Application Name", System.currentTimeMillis());  
Intent notificationIntent = new Intent(this, YourMainActivity.class);  
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);  
notification.setLatestEventInfo(this, "Your Application Name", "Your Service Name", pendingIntent);  
startForeground(1, notification);
使用Foreground方式,啓動後,你會看到在狀態列有一個你自定的icon, 經實際結果,跑了一天,Service從未被重啓過