上位机是安卓机,下位机是51,上位机(语音识别出指令如开关灯)与下位机(接蓝牙模块BLE:CC2451)通过蓝牙通信进行控制。
初步设想是语音识别部分用科大讯飞的sdk,然后再找个开源的蓝牙通信Demo来进行二次开发,无奈本人小白,无安卓开发经验,java零基础,虽然论坛上有很多现成的蓝牙控制单片机的例子,但是好像没有语音识别这一部分的,做得很难受。
以下贴出我的进展图、App闪退错误代码,以及半成品Demo(只实现了语音识别部分),希望有大牛指导一下。
开发环境:Android Studio 2.1.3
Gradle版本:com.android.tools.build:gradle:2.1.3
SDK版本:android-sdk_r24.4.1-windows
JDK版本:jdk1.8.0_201
Compile Sdk Version:API 25
Build Tools Version:28.0.3
半成品Demo:(https://download.csdn.net/download/qq_40093925/10975515)
**
**
package com.example.luoyn.speechdemo;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothServerSocket;
import android.bluetooth.BluetoothSocket;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Environment;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import android.os.Handler;
import com.google.android.gms.appindexing.Action;
import com.google.android.gms.appindexing.AppIndex;
import com.google.android.gms.common.api.GoogleApiClient;
import com.iflytek.cloud.ErrorCode;
import com.iflytek.cloud.InitListener;
import com.iflytek.cloud.RecognizerListener;
import com.iflytek.cloud.RecognizerResult;
import com.iflytek.cloud.SpeechConstant;
import com.iflytek.cloud.SpeechError;
import com.iflytek.cloud.SpeechRecognizer;
import com.iflytek.cloud.SpeechUtility;
import com.iflytek.cloud.ui.RecognizerDialog;
import com.iflytek.cloud.ui.RecognizerDialogListener;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.UUID;
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
//显示听写结果
private TextView textView;
//语音听写对象
private SpeechRecognizer speechRecognizer;
//语音听写UI
private RecognizerDialog recognizerDialog;
//是否显示听写UI
private boolean isShowDialog = true;
//缓存
private SharedPreferences sharedPreferences;
//用hashmap存储听写结果
private HashMap hashMap = new LinkedHashMap();
//引擎类型(云端或本地)
private String mEngineType = null;
//函数返回值
private int ret = 0;
private Toast toast;
//插入部分--------------------------------------------------------------------------------------
public static final int REQUEST_BT_ENABLE_CODE = 200;
public static final String BT_UUID = "00001101-0000-1000-8000-00805F9B34FB";//YahBoom_uuid
private BluetoothAdapter mBluetoothAdapter;//蓝牙适配器
private BlueToothStateReceiver mReceiver;//广播接收器
private ConnectThread mConnectThread; //客户端线程
private AcceptThread mAcceptThread; //服务端线程
private RecyclerView mRecyclerView;
private RvAdapter mRvAdapter;
private RecyclerView mMessageView;
private static MsgAdapter mMessageAdapter;
private EditText inputEt;
private static Handler mHandler = new Handler() {
@Override
public void dispatchMessage(Message msg) {
mMessageAdapter.addMessage((String) msg.obj);
}
};
/**
* ATTENTION: This was auto-generated to implement the App Indexing API.
* See https://g.co/AppIndexing/AndroidStudio for more information.
*/
private GoogleApiClient client;
//----------------------------------------------------------------------------------------------
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
initDate();
//插入部分--------------------------------------------------------------------------------------
initUI();
registerRec();
//------------------------------------------------------------------------------------------
// ATTENTION: This was auto-generated to implement the App Indexing API.
// See https://g.co/AppIndexing/AndroidStudio for more information.
client = new GoogleApiClient.Builder(this).addApi(AppIndex.API).build();
}
private void initDate() {
//初始化sdk 将自己申请的appid放到下面
//此句代码应该放在application中的,这里为了方便就直接放代码中了
SpeechUtility.createUtility(this, "appid=5c6abcfc");
speechRecognizer = SpeechRecognizer.createRecognizer(this, initListener);
recognizerDialog = new RecognizerDialog(this, initListener);
sharedPreferences = getSharedPreferences(this.getPackageName(), Context.MODE_PRIVATE);
toast = Toast.makeText(this, "", Toast.LENGTH_SHORT);
//这里我直接将引擎类型设置为云端,因为本地需要下载讯飞语记,这里为了方便直接使用云端
//有需要的朋友可以加个单选框 让用户选择云端或本地
mEngineType = SpeechConstant.TYPE_CLOUD;
}
private void initView() {
textView = (TextView) findViewById(R.id.tv);
}
//插入部分--------------------------------------------------------------------------------------
private void initUI() {
findViewById(R.id.open).setOnClickListener(this);
findViewById(R.id.close).setOnClickListener(this);
findViewById(R.id.start).setOnClickListener(this);
findViewById(R.id.stop).setOnClickListener(this);
findViewById(R.id.send).setOnClickListener(this);
inputEt = (EditText) findViewById(R.id.input);
mRecyclerView = (RecyclerView) findViewById(R.id.devices);
mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
mRvAdapter = new RvAdapter(this);
mRecyclerView.setAdapter(mRvAdapter);
mRvAdapter.setOnItemClickListener(new RvAdapter.OnItemClickListener() {
@Override
public void onClick(BluetoothDevice device) {
mConnectThread = new ConnectThread(device);
mConnectThread.start();
}
});
mMessageView = (RecyclerView) findViewById(R.id.msglist);
mMessageView.setLayoutManager(new LinearLayoutManager(this));
mMessageAdapter = new MsgAdapter(this);
mMessageView.setAdapter(mMessageAdapter);
}
private void openBT() {
if (mBluetoothAdapter == null) {
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
}
//1.设备不支持蓝牙,结束应用
if (mBluetoothAdapter == null) {
finish();
return;
}
//2.判断蓝牙是否打开
if (!mBluetoothAdapter.enable()) {
//没打开请求打开
Intent btEnable = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(btEnable, REQUEST_BT_ENABLE_CODE);
}
}
private void registerRec() {
//3.注册蓝牙广播
mReceiver = new BlueToothStateReceiver();
IntentFilter filter = new IntentFilter();
filter.addAction(BluetoothDevice.ACTION_FOUND);//搜多到蓝牙
filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);//搜索结束
registerReceiver(mReceiver, filter);
}
@Override
protected void onDestroy() {
if (mReceiver != null) {
unregisterReceiver(mReceiver);
}
super.onDestroy();
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_BT_ENABLE_CODE) {
if (resultCode == RESULT_OK) {
//用户允许打开蓝牙
mMessageAdapter.addMessage("用户同意打开蓝牙");
} else if (resultCode == RESULT_CANCELED) {
//用户取消打开蓝牙
mMessageAdapter.addMessage("用户拒绝打开蓝牙");
}
}
super.onActivityResult(requestCode, resultCode, data);
}
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.open:
openBT();
mMessageAdapter.addMessage("打开蓝牙");
if (mAcceptThread == null && mBluetoothAdapter != null) {
mAcceptThread = new AcceptThread();
mAcceptThread.start();
mMessageAdapter.addMessage("启动服务线程");
}
break;
case R.id.close:
mBluetoothAdapter.disable();
break;
case R.id.start:
if (mBluetoothAdapter != null) {
mRvAdapter.clearDevices();//开始搜索前清空上一次的列表
mBluetoothAdapter.startDiscovery();
mMessageAdapter.addMessage("开始搜索蓝牙");
} else {
openBT();
if (mBluetoothAdapter != null) {
mRvAdapter.clearDevices();//开始搜索前清空上一次的列表
mBluetoothAdapter.startDiscovery();
mMessageAdapter.addMessage("开始搜索蓝牙");
}
}
break;
case R.id.stop:
if (mBluetoothAdapter != null && mBluetoothAdapter.isDiscovering()) {
mBluetoothAdapter.cancelDiscovery();
}
break;
case R.id.send:
String msg = inputEt.getText().toString();
if (TextUtils.isEmpty(msg)) {
Toast.makeText(this, "消息为空", Toast.LENGTH_SHORT).show();
return;
}
if (mConnectThread != null) {//证明我主动去链接别人了
mConnectThread.write(msg);
} else if (mAcceptThread != null) {
mAcceptThread.write(msg);
}
mMessageAdapter.addMessage("发送消息:" + msg);
break;
}
}
@Override
public void onStart() {
super.onStart();
// ATTENTION: This was auto-generated to implement the App Indexing API.
// See https://g.co/AppIndexing/AndroidStudio for more information.
client.connect();
Action viewAction = Action.newAction(
Action.TYPE_VIEW, // TODO: choose an action type.
"Main Page", // TODO: Define a title for the content shown.
// TODO: If you have web page content that matches this app activity's content,
// make sure this auto-generated web page URL is correct.
// Otherwise, set the URL to null.
Uri.parse("http://host/path"),
// TODO: Make sure this auto-generated app URL is correct.
Uri.parse("android-app://com.example.luoyn.speechdemo/http/host/path")
);
AppIndex.AppIndexApi.start(client, viewAction);
}
@Override
public void onStop() {
super.onStop();
// ATTENTION: This was auto-generated to implement the App Indexing API.
// See https://g.co/AppIndexing/AndroidStudio for more information.
Action viewAction = Action.newAction(
Action.TYPE_VIEW, // TODO: choose an action type.
"Main Page", // TODO: Define a title for the content shown.
// TODO: If you have web page content that matches this app activity's content,
// make sure this auto-generated web page URL is correct.
// Otherwise, set the URL to null.
Uri.parse("http://host/path"),
// TODO: Make sure this auto-generated app URL is correct.
Uri.parse("android-app://com.example.luoyn.speechdemo/http/host/path")
);
AppIndex.AppIndexApi.end(client, viewAction);
client.disconnect();
}
class BlueToothStateReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(MainActivity.this, "触发广播", Toast.LENGTH_SHORT).show();
String action = intent.getAction();
switch (action) {
case BluetoothDevice.ACTION_FOUND:
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
Toast.makeText(MainActivity.this, "找到设备" + device.getName(), Toast.LENGTH_SHORT).show();
if (mRvAdapter != null) {
mRvAdapter.addDevice(device);
}
break;
case BluetoothAdapter.ACTION_DISCOVERY_FINISHED:
mMessageAdapter.addMessage("搜索结束");
break;
}
}
}
class ConnectThread extends Thread {
private BluetoothDevice mDevice;
private BluetoothSocket mSocket;
private InputStream btIs;
private OutputStream btOs;
private boolean canRecv;
private PrintWriter writer;
public ConnectThread(BluetoothDevice device) {
mDevice = device;
canRecv = true;
}
@Override
public void run() {
if (mDevice != null) {
try {
//获取套接字
BluetoothSocket temp = mDevice.createInsecureRfcommSocketToServiceRecord(UUID.fromString(BT_UUID));
//mDevice.createRfcommSocketToServiceRecord(UUID.fromString(BT_UUID));//sdk 2.3以下使用
mSocket = temp;
//发起连接请求
if (mSocket != null) {
mSocket.connect();
}
sendHandlerMsg("连接 " + mDevice.getName() + "成功!");
//获取输入输出流
btIs = mSocket.getInputStream();
btOs = mSocket.getOutputStream();
//通讯-接收消息
BufferedReader reader = new BufferedReader(new InputStreamReader(btIs, "UTF-8"));
String content = null;
while (canRecv) {
content = reader.readLine();
sendHandlerMsg("收到消息:" + content);
}
} catch (IOException e) {
e.printStackTrace();
sendHandlerMsg("错误:" + e.getMessage());
} finally {
try {
if (mSocket != null) {
mSocket.close();
}
//btIs.close();//两个输出流都依赖socket,关闭socket即可
//btOs.close();
} catch (IOException e) {
e.printStackTrace();
sendHandlerMsg("错误:" + e.getMessage());
}
}
}
}
private void sendHandlerMsg(String content) {
Message msg = mHandler.obtainMessage();
msg.what = 1001;
msg.obj = content;
mHandler.sendMessage(msg);
}
public void write(String msg) {
if (btOs != null) {
try {
if (writer == null) {
writer = new PrintWriter(new OutputStreamWriter(btOs, "UTF-8"), true);
}
writer.println(msg);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
writer.close();
sendHandlerMsg("错误:" + e.getMessage());
}
}
}
}
class AcceptThread extends Thread {
private BluetoothServerSocket mServerSocket;
private BluetoothSocket mSocket;
private InputStream btIs;
private OutputStream btOs;
private PrintWriter writer;
private boolean canAccept;
private boolean canRecv;
public AcceptThread() {
canAccept = true;
canRecv = true;
}
@Override
public void run() {
try {
//获取套接字
BluetoothServerSocket temp = mBluetoothAdapter.listenUsingInsecureRfcommWithServiceRecord("TEST", UUID.fromString(BT_UUID));
mServerSocket = temp;
//监听连接请求 -- 作为测试,只允许连接一个设备
if (mServerSocket != null) {
// while (canAccept) {
mSocket = mServerSocket.accept();
sendHandlerMsg("有客户端连接");
// }
}
//获取输入输出流
btIs = mSocket.getInputStream();
btOs = mSocket.getOutputStream();
//通讯-接收消息
BufferedReader reader = new BufferedReader(new InputStreamReader(btIs, "UTF-8"));
String content = null;
while (canRecv) {
content = reader.readLine();
sendHandlerMsg("收到消息:" + content);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (mSocket != null) {
mSocket.close();
}
// btIs.close();//两个输出流都依赖socket,关闭socket即可
// btOs.close();
} catch (IOException e) {
e.printStackTrace();
sendHandlerMsg("错误:" + e.getMessage());
}
}
}
private void sendHandlerMsg(String content) {
Message msg = mHandler.obtainMessage();
msg.what = 1001;
msg.obj = content;
mHandler.sendMessage(msg);
}
public void write(String msg) {
if (btOs != null) {
try {
if (writer == null) {
writer = new PrintWriter(new OutputStreamWriter(btOs, "UTF-8"), true);
}
writer.println(msg);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
writer.close();
sendHandlerMsg("错误:" + e.getMessage());
}
}
}
}
//----------------------------------------------------------------------------------------------
//开始听写
public void start(View view) {
textView.setText("");
hashMap.clear();
setParams();
if (isShowDialog) {
recognizerDialog.setListener(dialogListener);
recognizerDialog.show();
} else {
ret = speechRecognizer.startListening(recognizerListener);
if (ret != ErrorCode.SUCCESS) {
Log.e("tag", "听写失败,错误码" + ret);
}
}
}
//结束听写
public void stop(View view) {
Toast.makeText(this, "停止听写", Toast.LENGTH_SHORT).show();
if (isShowDialog) {
recognizerDialog.dismiss();
} else {
speechRecognizer.stopListening();
}
}
//初始化监听器
private InitListener initListener = new InitListener() {
@Override
public void onInit(int i) {
if (i != ErrorCode.SUCCESS) {
Log.e("tag", "初始化失败,错误码" + i);
}
}
};
//无UI监听器
private RecognizerListener recognizerListener = new RecognizerListener() {
@Override
public void onVolumeChanged(final int i, byte[] bytes) {
Log.e("tag", "返回数据大小" + bytes.length);
runOnUiThread(new Runnable() {
@Override
public void run() {
toast.setText("当前音量" + i);
}
});
}
@Override
public void onBeginOfSpeech() {
Log.e("tag", "开始说话");
}
@Override
public void onEndOfSpeech() {
Log.e("tag", "结束说话");
}
@Override
public void onResult(RecognizerResult recognizerResult, boolean b) {
if (recognizerResult != null) {
Log.e("tag", "听写结果:" + recognizerResult.getResultString());
printResult(recognizerResult);
}
}
@Override
public void onError(SpeechError speechError) {
Log.e("tag", "错误信息" + speechError.getPlainDescription(true));
}
@Override
public void onEvent(int i, int i1, int i2, Bundle bundle) {
// 以下代码用于获取与云端的会话id,当业务出错时将会话id提供给技术支持人员,可用于查询会话日志,定位出错原因
// 若使用本地能力,会话id为null
// if (SpeechEvent.EVENT_SESSION_ID == eventType) {
// String sid = obj.getString(SpeechEvent.KEY_EVENT_SESSION_ID);
// Log.d(TAG, "session id =" + sid);
// }
}
};
//有UI监听器
private RecognizerDialogListener dialogListener = new RecognizerDialogListener() {
@Override
public void onResult(RecognizerResult recognizerResult, boolean b) {
if (recognizerResult != null) {
Log.e("tag", "听写结果:" + recognizerResult.getResultString());
printResult(recognizerResult);
}
}
@Override
public void onError(SpeechError speechError) {
Log.e("tag", speechError.getPlainDescription(true));
}
};
//输出结果,将返回的json字段解析并在textVie中显示
private void printResult(RecognizerResult results) {
String text = JsonParser.parseIatResult(results.getResultString());
String sn = null;
// 读取json结果中的sn字段
try {
JSONObject resultJson = new JSONObject(results.getResultString());
sn = resultJson.optString("sn");
} catch (JSONException e) {
e.printStackTrace();
}
hashMap.put(sn, text);
StringBuffer resultBuffer = new StringBuffer();
for (String key : hashMap.keySet()) {
resultBuffer.append(hashMap.get(key));
}
textView.setText(resultBuffer.toString());
}
private void setParams() {
//清空参数
speechRecognizer.setParameter(SpeechConstant.PARAMS, null);
//设置引擎
speechRecognizer.setParameter(SpeechConstant.ENGINE_TYPE, mEngineType);
//设置返回数据类型
speechRecognizer.setParameter(SpeechConstant.RESULT_TYPE, "json");
//设置中文 普通话
speechRecognizer.setParameter(SpeechConstant.LANGUAGE, "zh_cn");
speechRecognizer.setParameter(SpeechConstant.ACCENT, "mandarin");
// 设置语音前端点:静音超时时间,即用户多长时间不说话则当做超时处理
speechRecognizer.setParameter(SpeechConstant.VAD_BOS,
sharedPreferences.getString("iat_vadbos_preference", "4000"));
// 设置语音后端点:后端点静音检测时间,即用户停止说话多长时间内即认为不再输入, 自动停止录音
speechRecognizer.setParameter(SpeechConstant.VAD_EOS,
sharedPreferences.getString("iat_vadeos_preference", "1000"));
// 设置标点符号,设置为"0"返回结果无标点,设置为"1"返回结果有标点
speechRecognizer.setParameter(SpeechConstant.ASR_PTT,
sharedPreferences.getString("iat_punc_preference", "0"));
// 设置音频保存路径,保存音频格式支持pcm、wav,设置路径为sd卡请注意WRITE_EXTERNAL_STORAGE权限
// 注:AUDIO_FORMAT参数语记需要更新版本才能生效
speechRecognizer.setParameter(SpeechConstant.AUDIO_FORMAT, "wav");
speechRecognizer.setParameter(SpeechConstant.ASR_AUDIO_PATH,
Environment.getExternalStorageDirectory() + "/msc/iat.wav");
}
}