Skip to content

Instantly share code, notes, and snippets.

@rowntreerob
Last active December 19, 2015 16:48
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rowntreerob/7de7a9761edf980347b1 to your computer and use it in GitHub Desktop.
Save rowntreerob/7de7a9761edf980347b1 to your computer and use it in GitHub Desktop.
Google speech API, full-duplex, android sample
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.util.Calendar;
import java.util.List;
import java.util.Scanner;
import java.util.Map.Entry;
import javax.net.ssl.HttpsURLConnection;
import org.codehaus.jackson.JsonNode;
import org.codehaus.jackson.JsonParseException;
import org.codehaus.jackson.map.JsonMappingException;
import org.codehaus.jackson.map.ObjectMapper;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Intent;
import android.media.MediaRecorder;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
import com.parse.ParseObject;
import com.parse.starter.ParseApplication;
// Android - block of code to implement full-duplex session with google speech-api
// interface : myfil below is a file recorded earlier ( code not shown )
// the file is FLAC encoded at 22050 in this instance. Container type for
// media does not matter ( googl speech-API full-duplex is a byte-stream interface )
// with required parms of (encode Type, sample-rate)
// AMR-NB; 8000 also works and is much easier than FLAC because android (4.2.2) does not
// include FLAC encoder. see https://github.com/Audioboo/audioboo-android for a FLAC encoder
// that requires 2 JNI interface classes on the java side and pretty much the entire
// src/build path beneath ./$audioboo/jni
// FLAC is better quality in orig recording and seems to afford better results from the API
// FLAC is alot more work in android than AMR-NB due to the jni requirement on the encoder.
public void onPostButton(View view){
File myfil = new File(Environment.getExternalStorageDirectory(), tmpFilNme);
if(!myfil.canRead())Log.d("ParseStarter", "FATAL no read access" );
//first is a GET for the speech-api DOWNSTREAM
// then a future exec for the UPSTREAM / chunked encoding used so as not to limit
// the POST body sz
// DOWN handler
Handler messageHandler = new Handler() {
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case 1: //GET DOWNSTREAM json id="@+id/comment"
String mtxt = msg.getData().getString("text");
if(mtxt.length() > 20){
final String f_msg = getText(mtxt);
handler.post(new Runnable() { // This thread runs in the UI
@Override
public void run() {
toastmsg(f_msg); // Update the UI
}
});
}
break;
case 2:
break;
}
}
}; //doDOWNSTRM Handler end
PAIR = MIN + (long)(Math.random() * ((MAX - MIN) + 1L));
// DOWN URL just like in curl full-duplex example plus the handler
downChannel(API_DOWN_URL,messageHandler);
//UPSTREAM channel. its servicing a thread and should have its own handler
Handler messageHandler2 = new Handler() {
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case 1: //GET DOWNSTREAM json
Log.d("ParseStarter", msg.getData().getString("post"));
break;
case 2:
Log.d("ParseStarter", msg.getData().getString("post"));
break;
}
}
}; // UPstream handler end
//UP chan, process the audio byteStream for interface to UrlConnection using 'chunked-encoding'
FileInputStream fis;
try {
fis = new FileInputStream(myfil);
FileChannel fc = fis.getChannel(); // Get the file's size and then map it into memory
int sz = (int)fc.size();
MappedByteBuffer bb = fc.map(FileChannel.MapMode.READ_ONLY, 0, sz);
byte[] data2 = new byte[bb.remaining()];
Log.d("ParseStarter", "mapfil "+ sz + " " +bb.remaining());
bb.get(data2);
// conform to the interface from the curl examples on full-duplex calls
// see curl examples full-duplex for more on 'PAIR'. Just a globally uniq value typ=long->String.
// API KEY value is part of value in UP_URL_p2
upChannel(API_UP_URL_p1 +PAIR +API_UP_URL_p2, messageHandler2, data2);
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
// Best-Practice? may be to have separate handler for each thread (Main-UI, UP, DOWN )
private void downChannel(String urlStr ,final Handler messageHandler) {
final String url = urlStr;
new Thread () {
Bundle b;
public void run() {
String response ="";
Message msg = Message.obtain();
msg.what=1;
// handler for DOWN channel http response stream - httpsUrlConn
// response handler should manage the connection.... ??
// assign a TIMEOUT Value that exceeds by a safe factor
// the amount of time that it will take to write the bytes
// to the UPChannel in a fashion that mimics a liveStream
// of the audio at the applicable Bitrate. BR=sampleRate * bits per sample
// Note that the TLS session uses "* SSLv3, TLS alert, Client hello (1): "
// to wake up the listener when there are additional bytes.
// The mechanics of the TLS session should be transparent. Just use
// httpsUrlConn and allow it enough time to do its work.
Scanner inStream = openHttpsConnection(url);
//process the stream and store it in StringBuilder
while(inStream.hasNextLine()){
b = new Bundle();
b.putString("text", inStream.nextLine());
msg.setData(b);
messageHandler.dispatchMessage(msg);
}
}
}.start();
}
private void upChannel(String urlStr ,final Handler messageHandler, byte[] arg3) {
final String murl = urlStr;
final byte[] mdata = arg3;
Log.d("ParseStarter", "upChan " + mdata.length);
new Thread () {
public void run() {
String response ="";
Message msg = Message.obtain();
msg.what=2;
Scanner inStream = openHttpsPostConnection(murl, mdata);
inStream.hasNext();
//process the stream and store it in StringBuilder
while(inStream.hasNextLine()){
response+=(inStream.nextLine());
Log.d("ParseStarter", "POST resp " +response.length());
}
Bundle b = new Bundle();
b.putString("post", response);
msg.setData(b);
// in.close(); // mind the resources
messageHandler.sendMessage(msg);
}
}.start();
}
//GET for DOWNSTREAM
private Scanner openHttpsConnection(String urlStr) {
InputStream in = null;
int resCode = -1;
Log.d("ParseStarter", "dwnURL " +urlStr);
try {
URL url = new URL(urlStr);
URLConnection urlConn = url.openConnection();
if (!(urlConn instanceof HttpsURLConnection)) {
throw new IOException ("URL is not an Https URL");
}
HttpsURLConnection httpConn = (HttpsURLConnection)urlConn;
httpConn.setAllowUserInteraction(false);
// TIMEOUT is required
httpConn.setInstanceFollowRedirects(true);
httpConn.setRequestMethod("GET");
httpConn.connect();
bit
resCode = httpConn.getResponseCode();
if (resCode == HttpsURLConnection.HTTP_OK) {
return new Scanner(httpConn.getInputStream());
}
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
//GET for UPSTREAM
private Scanner openHttpsPostConnection(String urlStr, byte[] data) {
InputStream in = null;
byte[] mextrad = data;
int resCode = -1;
OutputStream out = null;
// int http_status;
try {
URL url = new URL(urlStr);
URLConnection urlConn = url.openConnection();
if (!(urlConn instanceof HttpsURLConnection)) {
throw new IOException ("URL is not an Https URL");
}
HttpsURLConnection httpConn = (HttpsURLConnection)urlConn;
httpConn.setAllowUserInteraction(false);
httpConn.setInstanceFollowRedirects(true);
httpConn.setRequestMethod("POST");
httpConn.setDoOutput(true);
httpConn.setChunkedStreamingMode(0);
httpConn.setRequestProperty("Content-Type", "audio/x-flac; rate=22050");
// also worked with ("Content-Type", "audio/amr; rate=8000");
httpConn.connect();
try {
// this opens a connection, then sends POST & headers.
out = httpConn.getOutputStream();
//Note : if the audio is more than 15 seconds
// dont write it to UrlConnInputStream all in one block as this sample does.
// Rather, segment the byteArray and on intermittently, sleeping thread
// supply bytes to the urlConn Stream at a rate that approaches
// the bitrate ( =30K per sec. in this instance ).
Log.d("ParseStarter", "IO beg on data");
out.write(mextrad); // one big block supplied instantly to the underlying chunker wont work for duration > 15 s.
Log.d("ParseStarter", "IO fin on data");
// do you need the trailer?
// NOW you can look at the status.
resCode = httpConn.getResponseCode();
Log.d("ParseStarter", "POST OK resp " +httpConn.getResponseMessage().getBytes().toString());
if (resCode / 100 != 2) {
Log.d("ParseStarter", "POST bad io " );
}
} catch (IOException e) {
Log.d("ParseStarter", "FATAL " +e);
}
if (resCode == HttpsURLConnection.HTTP_OK) {
Log.d("ParseStarter", "OK RESP to POST return scanner " );
return new Scanner(httpConn.getInputStream());
}
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
@rowntreerob
Copy link
Author

private static final long MIN = 10000000;
private static final long MAX = 900000009999999L;
PAIR = MIN + (long)(Math.random() * ((MAX - MIN) + 1L));
DOWN = root + dwn

UP = root + up_p1 + PAIR + up_p2 + api_key

name="default_value_speech_duplx_root">https://www.google.com/speech-api/full-duplex/v1/
name="default_value_speech_duplx_dwn">down?maxresults=1&pair=
name="default_value_speech_duplx_up_p1">up?lang=en-US&lm=dictation&client=chromium&pair=
name="default_value_speech_duplx_up_p2">&key=
name="default_value_google_api_key">get a key from google

@rowntreerob
Copy link
Author

READ the SPEC on writing a LAST_CHUNK......
FINAL_CHUNK = new byte[] { '0', '\r', '\n', '\r', '\n' };

rfc 2616 , sect. 3.6.1 Chunked Transfer Coding

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment