Android Open Source - K6nele Recognizer Intent Service






From Project

Back to project page K6nele.

License

The source code is released under:

Apache License

If you think the Android project K6nele listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

/*
 * Copyright 2011-2012, Institute of Cybernetics at Tallinn University of Technology
 *//w  ww . ja  va  2  s  .c  o  m
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package ee.ioc.phon.android.speak;

import android.app.Service;

import android.content.Intent;

import android.os.Binder;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.Process;
import android.os.SystemClock;

import java.io.IOException;

import ee.ioc.phon.netspeechapi.recsession.ChunkedWebRecSession;
import ee.ioc.phon.netspeechapi.recsession.NotAvailableException;
import ee.ioc.phon.netspeechapi.recsession.RecSessionResult;


/**
 * TODO: remove this class and rewrite the clients to use SpeechRecognitionService
 *
 * @author Kaarel Kaljurand
 */
public class RecognizerIntentService extends Service {

  private static final String LOG_TAG = RecognizerIntentService.class.getName();

  private final IBinder mBinder = new RecognizerBinder();

  private volatile Looper mSendLooper;
  private volatile Handler mSendHandler;

  private Runnable mSendTask;

  private ChunkedWebRecSession mRecSession;

  private RawAudioRecorder mRecorder;

  private OnResultListener mOnResultListener;
  private OnErrorListener mOnErrorListener;

  private int mErrorCode;

  private int mChunkCount = 0;

  private long mStartTime = 0;

  public enum State {
    // Service created or resources released
    IDLE,
    // Recognizer session created
    INITIALIZED,
    // Started the recording
    RECORDING,
    // Finished recording, transcribing now
    PROCESSING,
    // Got an error
    ERROR;
  }

  private State mState = null;

  private AudioPauser mAudioPauser;


  public class RecognizerBinder extends Binder {
    public RecognizerIntentService getService() {
      return RecognizerIntentService.this;
    }
  }


  public interface OnResultListener {
    public boolean onResult(RecSessionResult result);
  }


  public interface OnErrorListener {
    public boolean onError(int errorCode, Exception e);
  }


  @Override
  public void onCreate() {
    Log.i(LOG_TAG, "onCreate");
    setState(State.IDLE);
  }


  @Override
  public IBinder onBind(Intent intent) {
    Log.i(LOG_TAG, "onBind");
    return mBinder;
  }


  @Override
  public void onDestroy() {
    Log.i(LOG_TAG, "onDestroy");
    releaseResources();
  }


  public void setOnResultListener(OnResultListener onResultListener) {
    mOnResultListener = onResultListener;
  }


  public void setOnErrorListener(OnErrorListener onErrorListener) {
    mOnErrorListener = onErrorListener;
  }


  public State getState() {
    return mState;
  }


  /**
   * @return time when the recording started
   */
  public long getStartTime() {
    return mStartTime;
  }


  /**
   * @return <code>true</code> iff currently recording or processing
   */
  public boolean isWorking() {
    State currentState = getState();
    return currentState == State.RECORDING || currentState == State.PROCESSING;
  }


  /**
   * @return length of the current recording in bytes
   */
  public int getLength() {
    if (mRecorder == null) {
      return 0;
    }
    return mRecorder.getLength();
  }


  /**
   * @return dB value of recent sound pressure
   */
  public float getRmsdb() {
    if (mRecorder == null) {
      return 0;
    }
    return mRecorder.getRmsdb();
  }


  /**
   * @return <code>true</code> iff currently recording non-speech
   */
  public boolean isPausing() {
    return mRecorder != null && mRecorder.isPausing();
  }


  /**
   * @return complete audio data from the beginning of the recording
   */
  public byte[] getCompleteRecording() {
    if (mRecorder == null) {
      return new byte[0];
    }
    return mRecorder.getCompleteRecording();
  }


  /**
   * @return complete audio data from the beginning of the recording, with wav-header
   */
  public byte[] getCompleteRecordingAsWav() {
    if (mRecorder == null) {
      return new byte[0];
    }
    return mRecorder.getCompleteRecordingAsWav();
  }


  /**
   * @return number of audio chunks sent to the server
   */
  public int getChunkCount() {
    return mChunkCount;
  }


  /**
   * @return error code that corresponds to the latest error state
   */
  public int getErrorCode() {
    return mErrorCode;
  }


  /**
   * <p>Tries to create a speech recognition session.</p>
   *
   * @return <code>true</code> iff there was no error
   */
  public boolean init(ChunkedWebRecSession recSession) {
    if (mState != State.IDLE && mState != State.ERROR) {
      processError(RecognizerIntent.RESULT_CLIENT_ERROR, null);
      return false;
    }
    mRecSession = recSession;
    try {
      mRecSession.create();
      setState(State.INITIALIZED);
      return true;
    } catch (IOException e) {
      processError(RecognizerIntent.RESULT_NETWORK_ERROR, e);
    } catch (NotAvailableException e) {
      processError(RecognizerIntent.RESULT_SERVER_ERROR, e);
    }
    return false;
  }


  /**
   * <p>Start recording with the given sample rate.</p>
   *
   * @param sampleRate sample rate in Hz, e.g. 16000
   */
  public boolean start(int sampleRate) {
    if (mState != State.INITIALIZED) {
      processError(RecognizerIntent.RESULT_CLIENT_ERROR, null);
      return false;
    }
    // Stop the audio
    mAudioPauser = new AudioPauser(this);
    mAudioPauser.pause();
    try {
      startRecording(sampleRate);
      mStartTime = SystemClock.elapsedRealtime();
      startChunkSending(Constants.TASK_INTERVAL_SEND, Constants.TASK_DELAY_SEND, false);
      setState(State.RECORDING);
      return true;
    } catch (IOException e) {
      processError(RecognizerIntent.RESULT_AUDIO_ERROR, e);
    }
    return false;
  }


  /**
   * <p>Stops the recording, finishes chunk sending, sends off the
   * last chunk (in another thread).</p>
   */
  public boolean stop() {
    if (mState != State.RECORDING || mRecorder == null) {
      processError(RecognizerIntent.RESULT_CLIENT_ERROR, null);
      return false;
    }
    mRecorder.stop();
    mSendHandler.removeCallbacks(mSendTask);
    transcribe(mRecorder.consumeRecording());
    if (mAudioPauser != null) {
      mAudioPauser.resume();
    }
    return true;
  }


  /**
   * <p>This can be called by clients who want to skip the recording
   * steps, but who have existing audio data which they want to transcribe.</p>
   *
   * @param bytes array of bytes to be transcribed
   */
  public boolean transcribe(final byte[] bytes) {
    if (mState != State.RECORDING && mState != State.INITIALIZED) {
      processError(RecognizerIntent.RESULT_CLIENT_ERROR, null);
      return false;
    }
    new Thread(new Runnable() {
      public void run() {
        transcribe_aux(bytes);
      }
    }).start();
    setState(State.PROCESSING);
    return true;
  }


  private void transcribe_aux(byte[] bytes) {
    try {
      sendChunk(bytes, true);
      RecSessionResult result = getResult();
      if (result == null || result.getLinearizations().isEmpty()) {
        processError(RecognizerIntent.RESULT_NO_MATCH, null);
      } else {
        processResult(result);
      }
    } catch (IOException e) {
      processError(RecognizerIntent.RESULT_NETWORK_ERROR, e);
    }
  }


  /**
   * <p>Starting chunk sending in a separate thread so that slow internet would not block the UI.</p>
   */
  private void startChunkSending(final int interval, int delay, final boolean consumeAll) {
    mChunkCount = 0;
    HandlerThread thread = new HandlerThread("SendHandlerThread", Process.THREAD_PRIORITY_BACKGROUND);
    thread.start();
    mSendLooper = thread.getLooper();
    mSendHandler = new Handler(mSendLooper);

    mSendTask = new Runnable() {
      public void run() {
        if (mRecorder != null && mRecorder.getState() == RawAudioRecorder.State.RECORDING) {
          try {
            sendChunk(mRecorder.consumeRecording(), consumeAll);
          } catch (IOException e) {
            processError(RecognizerIntent.RESULT_NETWORK_ERROR, e);
            return;
          }
          if (consumeAll == false) {
            mSendHandler.postDelayed(this, interval);
          }
        }
      }
    };
    mSendHandler.postDelayed(mSendTask, delay);
  }


  /**
   * <p>Starts recording from the microphone with the given sample rate.</p>
   *
   * @throws IOException if recorder could not be created
   */
  private void startRecording(int recordingRate) throws IOException {
    mRecorder = new RawAudioRecorder(recordingRate);
    if (mRecorder.getState() == RawAudioRecorder.State.ERROR) {
      throw new IOException(getString(R.string.errorCantCreateRecorder));
    }

    if (mRecorder.getState() != RawAudioRecorder.State.READY) {
      throw new IOException(getString(R.string.errorCantCreateRecorder));
    }

    mRecorder.start();

    if (mRecorder.getState() != RawAudioRecorder.State.RECORDING) {
      throw new IOException(getString(R.string.errorCantCreateRecorder));
    }
  }


  /**
   * <p>We kill the running processes in this order:
   * chunk sending, recognizer session, audio recorder.</p>
   * 
   * <p>Note that mRecorder.release() can be called in any state.
   * After that the recorder object is no longer available,
   * so we should set it to <code>null</code>.</p>
   */
  private void releaseResources() {
    if (mSendLooper != null) {
      mSendLooper.quit();
      mSendLooper = null;
    }

    if (mSendHandler != null) {
      mSendHandler.removeCallbacks(mSendTask);
    }

    if (mRecSession != null && ! mRecSession.isFinished()) {
      mRecSession.cancel();
    }

    if (mRecorder != null) {
      mRecorder.release();
      mRecorder = null;
    }
    if (mAudioPauser != null) {
      mAudioPauser.resume();
    }
  }


  private RecSessionResult getResult() throws IOException {
    if (mRecSession == null) {
      return null;
    }
    return mRecSession.getResult();
  }


  /**
   * <p>Note that this call can make sense even if there are 0 bytes to be sent.
   * This is the case when it is the last chunk to be sent. To properly close
   * the connection one must always call sendChunk with <code>isLast == true</code>.</p>
   *
   * @param bytes byte array representing the audio data
   * @param isLast indicates that this is the last chunk that is sent
   * @throws IOException 
   */
  private void sendChunk(byte[] bytes, boolean isLast) throws IOException {
    if (mRecSession != null && ! mRecSession.isFinished()) {
      mRecSession.sendChunk(bytes, isLast);
      mChunkCount++;
      if (isLast) {
        Log.i(LOG_TAG, "sendChunk: FINAL: " + bytes.length);
      } else {
        Log.i(LOG_TAG, "sendChunk: " + bytes.length);
      }
    } else {
      Log.e(LOG_TAG, "sendChunk: recSession is not available");
    }
  }


  private void setState(State state) {
    Log.i(LOG_TAG, "State changed to: " + state);
    mState = state;
  }


  // TODO: not sure what stopSelf does
  private void processResult(RecSessionResult result) {
    if (mOnResultListener != null) {
      mOnResultListener.onResult(result);
    }
    releaseResources();
    setState(State.IDLE);
    stopSelf();
  }


  private void processError(int errorCode, Exception e) {
    mErrorCode = errorCode;
    if (mOnErrorListener != null) {
      mOnErrorListener.onError(errorCode, e);
    }
    releaseResources();
    setState(State.ERROR);
  }
}




Java Source Code List

ee.ioc.phon.android.speak.AboutActivity.java
ee.ioc.phon.android.speak.AppListActivity.java
ee.ioc.phon.android.speak.AppListCursorAdapter.java
ee.ioc.phon.android.speak.AudioCue.java
ee.ioc.phon.android.speak.AudioPauser.java
ee.ioc.phon.android.speak.Caller.java
ee.ioc.phon.android.speak.ChunkedWebRecSessionBuilder.java
ee.ioc.phon.android.speak.Constants.java
ee.ioc.phon.android.speak.DetailsActivity.java
ee.ioc.phon.android.speak.ExecutableString.java
ee.ioc.phon.android.speak.Executable.java
ee.ioc.phon.android.speak.Extras.java
ee.ioc.phon.android.speak.GetLanguageDetailsReceiver.java
ee.ioc.phon.android.speak.GrammarListActivity.java
ee.ioc.phon.android.speak.Log.java
ee.ioc.phon.android.speak.MicButton.java
ee.ioc.phon.android.speak.OnSwipeTouchListener.java
ee.ioc.phon.android.speak.PackageNameRegistry.java
ee.ioc.phon.android.speak.PreferencesRecognitionServiceHttp.java
ee.ioc.phon.android.speak.PreferencesRecognitionServiceWs.java
ee.ioc.phon.android.speak.Preferences.java
ee.ioc.phon.android.speak.RawAudioRecorder.java
ee.ioc.phon.android.speak.RecognizerIntentActivity.java
ee.ioc.phon.android.speak.RecognizerIntentListActivity.java
ee.ioc.phon.android.speak.RecognizerIntentService.java
ee.ioc.phon.android.speak.RecognizerIntent.java
ee.ioc.phon.android.speak.ServerListActivity.java
ee.ioc.phon.android.speak.SpeechRecognitionService.java
ee.ioc.phon.android.speak.Utils.java
ee.ioc.phon.android.speak.VoiceImeService.java
ee.ioc.phon.android.speak.VoiceImeView.java
ee.ioc.phon.android.speak.WebSocketRecognizer.java
ee.ioc.phon.android.speak.WebSocketResponse.java
ee.ioc.phon.android.speak.demo.AbstractRecognizerDemoActivity.java
ee.ioc.phon.android.speak.demo.ExtrasDemo.java
ee.ioc.phon.android.speak.demo.RepeaterDemo.java
ee.ioc.phon.android.speak.demo.SimpleDemo.java
ee.ioc.phon.android.speak.demo.VoiceSearchDemo.java
ee.ioc.phon.android.speak.provider.App.java
ee.ioc.phon.android.speak.provider.AppsContentProvider.java
ee.ioc.phon.android.speak.provider.BaseColumnsImpl.java
ee.ioc.phon.android.speak.provider.FileContentProvider.java
ee.ioc.phon.android.speak.provider.Grammar.java
ee.ioc.phon.android.speak.provider.Server.java