Last active
December 19, 2015 16:48
-
-
Save rowntreerob/7de7a9761edf980347b1 to your computer and use it in GitHub Desktop.
Google speech API, full-duplex, android sample
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} |
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
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