com.oneguy.recognize.engine.GoogleStreamingEngine.java Source code

Java tutorial

Introduction

Here is the source code for com.oneguy.recognize.engine.GoogleStreamingEngine.java

Source

/*******************************************************************************
 * Copyright (c) 2012 Zuoshu (zuoshu.wuhan@gmail.com).
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the GNU Lesser General Public License v3.0
 * which accompanies this distribution, and is available at
 * http://www.gnu.org/licenses/lgpl-3.0.html
 * 
 * Contributors:
 *     Zuoshu - initial API and implementation
 ******************************************************************************/

package com.oneguy.recognize.engine;

import java.io.IOException;
import java.util.Random;

import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.params.CoreConnectionPNames;
import org.apache.http.util.EntityUtils;

import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;

import com.oneguy.recognize.BuildConfig;
import com.oneguy.recognize.engine.StreamResponse.Status;
import com.oneguy.recognize.recognize.EngineResultListener;

public class GoogleStreamingEngine extends AbstractEngine implements Runnable, StreamListener {
    private static final String TAG = "GoogleStreamingEngine";
    private static final int TIMEOUT = 10 * 1000;
    private static final int TRY_STOP_TIME = 100;
    private static final int THREAD_TRY_TIME = 20;

    public static final int EVENT_START = 3001;
    public static final int EVENT_STOP = 3002;
    public static final int EVENT_SHUTDOWN = 3003;
    public static final int EVENT_AUDIO_CHUNK = 3004;
    public static final int EVENT_RESPONSE = 3005;
    public static final int EVENT_STREAM_ERROR = 3006;

    private static final int STATE_IDLE = 1001;
    private static final int STATE_UP_STREAM_CONNECTED = 1002;
    private static final int STATE_WAITING_DOWNSTREAM_RESULTS = 1003;
    private static final int STATE_SHUTDOWN = 1004;

    private static final String GOOGLE_API_KEY = "AIzaSyBHDrl33hwRp4rMQY0ziRbj8K9LPA6vUCY";
    private static final String BASE_URL = "https://www.google.com/speech-api/full-duplex/v1";
    private static final String UP_STREAM_PRFX = "/up?";
    private static final String DOWN_STREAM_PRFX = "/down?";

    private int mState;
    private EngineResultListener mRecognizeListener;
    private Handler mHandler;
    private GetResponseRunnable downStreamRunnable;
    private Stream upStream;
    private boolean firstRun = true;
    private String mPairKey;

    public GoogleStreamingEngine() {
        // set url when connect streams
        upStream = new Stream("upStream", this, "");
        upStream.setConnectTimeout(TIMEOUT);
        upStream.setSocketTimeout(TIMEOUT);
        upStream.setMethod(Stream.METHOD_POST);
        upStream.setStreamListener(this);
        // downStreamRunnable = new GetResponseRunnable();
        firstRun = true;
        mState = STATE_IDLE;
    }

    @Override
    public void setRecognizeListener(EngineResultListener l) {
        mRecognizeListener = l;
    }

    public void setMimeType(String mimeType) {
        super.setMimeType(mimeType);
        upStream.setContentType(mimeType);
    }

    private void initThread() {
        new Thread(this).start();
        int tryTimes = 0;
        while (mHandler == null && tryTimes < THREAD_TRY_TIME) {
            try {
                Log.d(TAG, "wait engine init!");
                Thread.sleep(TRY_STOP_TIME);
                tryTimes++;
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    public void start() {
        if (firstRun) {
            initThread();
            firstRun = false;
        }
        if (mHandler == null) {
            Log.e(TAG, "can not start,run engine fail!");
            return;
        }
        mHandler.sendEmptyMessage(EVENT_START);
    }

    @Override
    public void stop() {
        if (mHandler == null) {
            Log.e(TAG, "can not stop,run engine in a thread first!");
            return;
        }
        mHandler.sendEmptyMessage(EVENT_STOP);
    }

    @Override
    public void shutdown() {
        if (mHandler == null) {
            Log.e(TAG, "can not shutdown,run engine in a thread first!");
            return;
        }
        mHandler.sendEmptyMessage(EVENT_SHUTDOWN);
    }

    @Override
    public void takeAudioChunk(byte[] chunk) {
        if (mHandler == null) {
            Log.e(TAG, "can not transmit,run engine in a thread first!");
            return;
        }
        if (chunk == null) {
            return;
        }
        Message msg = new Message();
        msg.what = EVENT_AUDIO_CHUNK;
        msg.obj = chunk;
        mHandler.sendMessage(msg);
    }

    @Override
    public void run() {
        Looper.prepare();
        mHandler = new Handler() {
            @Override
            public synchronized void handleMessage(Message msg) {
                int event = msg.what;
                Object arg = msg.obj;
                Log.d(TAG, "engine state:" + getStateName(mState) + " event:" + getEventName(event));
                switch (mState) {
                case STATE_IDLE:
                    switch (event) {
                    case EVENT_START:
                        connectUpStream();
                        break;
                    case EVENT_SHUTDOWN:
                        shutAll();
                        break;
                    case EVENT_STREAM_ERROR:
                        abortWithError();
                        break;
                    case EVENT_STOP:
                    case EVENT_AUDIO_CHUNK:
                    case EVENT_RESPONSE:
                        Log.d(TAG, "drop event:" + event + " state:IDLE");
                        break;
                    }
                    break;
                case STATE_UP_STREAM_CONNECTED:
                    switch (event) {
                    case EVENT_STOP:
                        sendStopPacketAndGetResponse();
                        break;
                    case EVENT_SHUTDOWN:
                        shutAll();
                        break;
                    case EVENT_AUDIO_CHUNK:
                        transmitAudioUpstream((byte[]) arg);
                        break;
                    case EVENT_STREAM_ERROR:
                        abortWithError();
                        break;
                    case EVENT_START:
                    case EVENT_RESPONSE:
                        Log.d(TAG, "drop event:" + event + " state:IDLE");
                        break;
                    }
                    break;
                case STATE_WAITING_DOWNSTREAM_RESULTS:
                    switch (event) {
                    case EVENT_START:
                        break;
                    case EVENT_STOP:
                        break;
                    case EVENT_SHUTDOWN:
                        shutAll();
                        break;
                    case EVENT_AUDIO_CHUNK:
                        break;
                    case EVENT_RESPONSE:
                        processResponse((StreamResponse) arg);
                        break;
                    case EVENT_STREAM_ERROR:
                        abortWithError();
                        break;
                    }
                    break;
                case STATE_SHUTDOWN:
                    Log.e(TAG, "impossible,STATE_SHUTDOWN should not receive event!");
                    break;
                }
            }

        };
        Looper.loop();
    }

    public Handler getHandler() {
        return mHandler;
    }

    private void connectUpStream() {
        mPairKey = generateRequestPairKey();
        upStream.setUrl(buildUpUrl(mPairKey));
        upStream.setPairKey(mPairKey);
        upStream.setMethod(Stream.METHOD_POST);
        if (upStream.connect2()) {
            Log.d(TAG, "engine->STATE_UP_STREAM_CONNECTED");
            mState = STATE_UP_STREAM_CONNECTED;
        }
    }

    boolean firstTransmit = true;

    private synchronized void transmitAudioUpstream(byte[] chunk) {
        upStream.transmitData(chunk);
        //      if (firstTransmit) {
        //         firstTransmit = false;
        //         new Thread(new Runnable() {
        //
        //            @Override
        //            public void run() {
        //               fetchResults();
        //            }
        //         }).start();
        //      }
    }

    private void sendStopPacketAndGetResponse() {
        sendStopPacket();
        // fetchResults();
        upStream.getResponse();
        Log.d(TAG, "engine->STATE_WAITING_DOWNSTREAM_RESULTS");
        mState = STATE_WAITING_DOWNSTREAM_RESULTS;
    }

    private void sendStopPacket() {
        byte[] endPacket = new byte[100];
        transmitAudioUpstream(endPacket);
        Log.d(TAG, "engine->STATE_WAITING_DOWNSTREAM_RESULTS");
        mState = STATE_WAITING_DOWNSTREAM_RESULTS;
        // upStream.getResponse();
    }

    private void fetchResults() {
        Log.d(TAG, "engine->fetchResults");
        byte[] data = getHttpData(buildDownUrl(mPairKey));
        // TODO post data to handler
        if (data != null) {
            StreamResponse response = new StreamResponse(null, Status.SUCCESS);
            response.setResponse(data);
            onStreamResponse(response);
        } else {
            StreamResponse response = new StreamResponse(null, Status.ERROR);
            onStreamResponse(response);
        }
    }

    private void shutAll() {
        upStream.disconnect();
        mHandler.getLooper().quit();
    }

    private void abortSilently() {
        abort(SPEECH_RECOGNITION_ERROR_NONE);
    }

    private void abortWithError() {
        Log.d(TAG, "abortWithError");
        abort(SPEECH_RECOGNITION_ERROR_NETWORK);
    }

    private void abort(int errorCode) {
        if (errorCode != SPEECH_RECOGNITION_ERROR_NONE) {
            if (mRecognizeListener != null) {
                mRecognizeListener.onError(errorCode);
            }
        }
        upStream.reset();
        Log.d(TAG, "engine->IDLE");
        mState = STATE_IDLE;
    }

    private String generateRequestPairKey() {
        Random random = new Random(System.currentTimeMillis());
        long value = random.nextLong();
        return Long.toHexString(value);
    }

    private String buildUpUrl(String pairKey) {
        StringBuilder upUrl = new StringBuilder();
        upUrl.append(BASE_URL);
        upUrl.append(UP_STREAM_PRFX);
        upUrl.append("key=" + GOOGLE_API_KEY);
        upUrl.append("&pair=");
        upUrl.append(pairKey);
        upUrl.append("&output=pb");
        upUrl.append("&lang=en-us");
        upUrl.append("&pFilter=0");
        upUrl.append("&client=chromium");
        upUrl.append("&maxAlternatives=1");
        upUrl.append("&continuous");
        upUrl.append("&interim");
        return upUrl.toString();
    }

    private String buildDownUrl(String pairKey) {
        StringBuilder downUrl = new StringBuilder();
        downUrl.append(BASE_URL);
        downUrl.append(DOWN_STREAM_PRFX);
        // downUrl.append("key=" + GOOGLE_API_KEY);
        downUrl.append("pair=");
        downUrl.append(pairKey);
        downUrl.append("&output=pb");
        return downUrl.toString();
    }

    private void reset() {
        firstTransmit = true;
        upStream.reset();
        mState = STATE_IDLE;
    }

    @Override
    public void onStreamResponse(StreamResponse response) {
        if (mState == STATE_SHUTDOWN) {
            Log.d(TAG, "engine is SHUTDOWN,ignore response:" + response.toString());
        } else {
            Log.d(TAG, "response:" + response.toString());
            Message msg = new Message();
            msg.what = EVENT_RESPONSE;
            msg.obj = response;
            mHandler.sendMessage(msg);
        }
    }

    @Override
    public void onStreamError(int errorCode) {
        if (mState == STATE_SHUTDOWN) {
            Log.d(TAG, "engine is SHUTDOWN,ignore errorcode:" + errorCode);
        } else {
            mHandler.sendEmptyMessage(EVENT_STREAM_ERROR);
        }
    }

    private void processResponse(StreamResponse response) {
        //      if (response != null && response.getSource() == upStream) {
        //         // ignore upstream response
        //         return;
        //      }

        if ((response == null || response.getStatus() == Status.ERROR)
                && (response.getResponse() == null || response.getResponse().length == 0)) {
            abortWithError();
            return;
        }

        //      if (response.getSource() == upStream) {
        //         // ignore upstream response
        //         Log.d(TAG, "response.getSource() == upStream");
        //         return;
        //      } else {
        //         Log.d(TAG, "response source:" + response.getSource());
        //      }
        ChunkBuffer cb = new ChunkBuffer();
        cb.transform(response.getResponse());
        if (cb.hasChunk()) {
            String result = cb.getBestResult();
            if (mRecognizeListener != null) {
                mRecognizeListener.onResult(result);
            }
        } else {
            if (mRecognizeListener != null) {
                mRecognizeListener.onError(SPEECH_RECOGNITION_ERROR_RECOGNIZE);
            }
        }
        reset();
    }

    byte[] getHttpData(String url) {
        String TAG = "getHttpData";
        HttpGet httpGet = new HttpGet(url);
        byte[] result = null;
        try {
            HttpClient client = new DefaultHttpClient();
            client.getParams().setParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, TIMEOUT);
            client.getParams().setParameter(CoreConnectionPNames.SO_TIMEOUT, TIMEOUT);
            Log.d(TAG, "HTTP GET:" + httpGet.getURI() + " method:" + httpGet.getMethod());
            HttpResponse httpResponse = client.execute(httpGet);
            if (httpResponse.getStatusLine().getStatusCode() == 200) {
                result = EntityUtils.toByteArray(httpResponse.getEntity());
                Log.d(TAG, new String(result));
                StreamResponse response = new StreamResponse(null, Status.SUCCESS);
                response.setResponse(result);
                response.setResponseCode(httpResponse.getStatusLine().getStatusCode());
                Message msg = new Message();
                msg.what = EVENT_RESPONSE;
                msg.obj = response;
                mHandler.sendMessage(msg);
            }
        } catch (ClientProtocolException e) {
            onError(e);
            e.printStackTrace();
        } catch (IOException e) {
            onError(e);
            e.printStackTrace();
        }
        return result;
    }

    boolean hasGetResponse = false;

    private static final int GET_REPONSE_INTERNAL = 100;

    class GetResponseRunnable implements Runnable {
        String url = "";

        public void setUrl(String url) {
            this.url = url;
        }

        @Override
        public void run() {
            getHttpData(url);
            if (!hasGetResponse) {
                mHandler.postDelayed(this, GET_REPONSE_INTERNAL);
            }
        }

        byte[] getHttpData(String url) {
            String TAG = "getHttpData";
            HttpGet httpGet = new HttpGet(url);
            byte[] result = null;
            try {
                HttpClient client = new DefaultHttpClient();
                client.getParams().setParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, TIMEOUT);
                client.getParams().setParameter(CoreConnectionPNames.SO_TIMEOUT, TIMEOUT);
                Log.d(TAG, "HTTP GET:" + httpGet.getURI() + " method:" + httpGet.getMethod());
                HttpResponse httpResponse = client.execute(httpGet);
                if (httpResponse.getStatusLine().getStatusCode() == 200) {
                    result = EntityUtils.toByteArray(httpResponse.getEntity());
                    StreamResponse response = new StreamResponse(null, Status.SUCCESS);
                    response.setResponse(result);
                    response.setResponseCode(httpResponse.getStatusLine().getStatusCode());
                    Message msg = new Message();
                    msg.what = EVENT_RESPONSE;
                    mHandler.sendMessage(msg);
                }
            } catch (ClientProtocolException e) {
                onError(e);
                e.printStackTrace();
            } catch (IOException e) {
                onError(e);
                e.printStackTrace();
            }
            return result;
        }

    }

    private void onError(Exception e) {
        e.printStackTrace();
        onStreamError(SPEECH_RECOGNITION_ERROR_NETWORK);
    }

    private String getEventName(int event) {
        switch (event) {
        case EVENT_START:
            return "EVENT_START";
        case EVENT_STOP:
            return "EVENT_STOP";
        case EVENT_SHUTDOWN:
            return "EVENT_SHUTDOWN";
        case EVENT_AUDIO_CHUNK:
            return "EVENT_AUDIO_CHUNK";
        case EVENT_RESPONSE:
            return "EVENT_DOWNSTREAM_RESPONSE";
        case EVENT_STREAM_ERROR:
            return "EVENT_STREAM_ERROR";
        default:
            return "unknown:" + event;
        }
    }

    private String getStateName(int state) {
        switch (state) {
        case STATE_IDLE:
            return "STATE_IDLE";
        case STATE_UP_STREAM_CONNECTED:
            return "STATE_UP_STREAM_CONNECTED";
        case STATE_WAITING_DOWNSTREAM_RESULTS:
            return "STATE_WAITING_DOWNSTREAM_RESULTS";
        default:
            return "unknown:" + state;
        }
    }
}