孔庆威的博客

精于心,简于形


  • 首页

  • 分类

  • 归档

  • 标签

灵云语音合成

发表于 2016-08-12 | 分类于 语音 |

转载请说明出处!
作者:kqw攻城狮
出处:个人站 | CSDN


注册

官网

注册比较简单,就不做过多介绍了,注册万应用以后,在后台创建自己的应用,创建完应用以后需要给应用开通对应的语音能力。

开通语音能力

集成

下载灵云SDK

如果使用在线功能,下载对应的SDK,里面有jar包和so,就可以满足需求了。如果要使用离线的语音功能,还需要下载灵云资源文件

源码

GitHub

灵云在线语音合成

权限

1
2
3
4
5
6
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

配置类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package kong.qingwei.kqwhcittsdemo;
/**
* Created by kqw on 2016/8/12.
* 灵云配置信息
*/
public final class ConfigUtil {
/**
* 灵云APP_KEY
*/
public static final String APP_KEY = "填入自己的APP KEY";
/**
* 开发者密钥
*/
public static final String DEVELOPER_KEY = "填入自己的DEVELOPER KEY";
/**
* 灵云云服务的接口地址
*/
public static final String CLOUD_URL = "test.api.hcicloud.com:8888";
/**
* 需要运行的灵云能力
*/
// public static final String CAP_KEY = "tts.local.synth";
public static final String CAP_KEY = "tts.cloud.wangjing";
}

封装灵云语音的初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
package kong.qingwei.kqwhcittsdemo;
import android.app.Activity;
import android.os.Environment;
import android.util.Log;
import android.widget.Toast;
import com.sinovoice.hcicloudsdk.api.HciCloudSys;
import com.sinovoice.hcicloudsdk.common.AuthExpireTime;
import com.sinovoice.hcicloudsdk.common.HciErrorCode;
import com.sinovoice.hcicloudsdk.common.InitParam;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
/**
* Created by kqw on 2016/8/12.
* 初始化灵云语音
*/
public class HciUtil {
private static final String TAG = "HciUtil";
private Activity mActivity;
private final String mConfigStr;
public HciUtil(Activity activity) {
mActivity = activity;
// 加载信息,返回InitParam, 获得配置参数的字符串
InitParam initParam = getInitParam();
mConfigStr = initParam.getStringConfig();
}
public boolean initHci() {
// 初始化
int errCode = HciCloudSys.hciInit(mConfigStr, mActivity);
if (errCode != HciErrorCode.HCI_ERR_NONE && errCode != HciErrorCode.HCI_ERR_SYS_ALREADY_INIT) {
Toast.makeText(mActivity, "hciInit error: " + HciCloudSys.hciGetErrorInfo(errCode), Toast.LENGTH_SHORT).show();
return false;
}
// 获取授权/更新授权文件 :
errCode = checkAuthAndUpdateAuth();
if (errCode != HciErrorCode.HCI_ERR_NONE) {
// 由于系统已经初始化成功,在结束前需要调用方法hciRelease()进行系统的反初始化
Toast.makeText(mActivity, "CheckAuthAndUpdateAuth error: " + HciCloudSys.hciGetErrorInfo(errCode), Toast.LENGTH_SHORT).show();
HciCloudSys.hciRelease();
return false;
}
return true;
}
/**
* 加载初始化信息
*
* @return 系统初始化参数
*/
private InitParam getInitParam() {
String authDirPath = mActivity.getFilesDir().getAbsolutePath();
// 前置条件:无
InitParam initparam = new InitParam();
// 授权文件所在路径,此项必填
initparam.addParam(InitParam.AuthParam.PARAM_KEY_AUTH_PATH, authDirPath);
// 是否自动访问云授权,详见 获取授权/更新授权文件处注释
initparam.addParam(InitParam.AuthParam.PARAM_KEY_AUTO_CLOUD_AUTH, "no");
// 灵云云服务的接口地址,此项必填
initparam.addParam(InitParam.AuthParam.PARAM_KEY_CLOUD_URL, ConfigUtil.CLOUD_URL);
// 开发者Key,此项必填,由捷通华声提供
initparam.addParam(InitParam.AuthParam.PARAM_KEY_DEVELOPER_KEY, ConfigUtil.DEVELOPER_KEY);
// 应用Key,此项必填,由捷通华声提供
initparam.addParam(InitParam.AuthParam.PARAM_KEY_APP_KEY, ConfigUtil.APP_KEY);
// 配置日志参数
String sdcardState = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(sdcardState)) {
String sdPath = Environment.getExternalStorageDirectory().getAbsolutePath();
String packageName = mActivity.getPackageName();
String logPath = sdPath + File.separator + "sinovoice" + File.separator + packageName + File.separator + "log" + File.separator;
// 日志文件地址
File fileDir = new File(logPath);
if (!fileDir.exists()) {
fileDir.mkdirs();
}
// 日志的路径,可选,如果不传或者为空则不生成日志
initparam.addParam(InitParam.LogParam.PARAM_KEY_LOG_FILE_PATH, logPath);
// 日志数目,默认保留多少个日志文件,超过则覆盖最旧的日志
initparam.addParam(InitParam.LogParam.PARAM_KEY_LOG_FILE_COUNT, "5");
// 日志大小,默认一个日志文件写多大,单位为K
initparam.addParam(InitParam.LogParam.PARAM_KEY_LOG_FILE_SIZE, "1024");
// 日志等级,0=无,1=错误,2=警告,3=信息,4=细节,5=调试,SDK将输出小于等于logLevel的日志信息
initparam.addParam(InitParam.LogParam.PARAM_KEY_LOG_LEVEL, "5");
}
return initparam;
}
/**
* 获取授权
*
* @return 授权结果
*/
private int checkAuthAndUpdateAuth() {
// 获取系统授权到期时间
int initResult;
AuthExpireTime objExpireTime = new AuthExpireTime();
initResult = HciCloudSys.hciGetAuthExpireTime(objExpireTime);
if (initResult == HciErrorCode.HCI_ERR_NONE) {
// 显示授权日期,如用户不需要关注该值,此处代码可忽略
Date date = new Date(objExpireTime.getExpireTime() * 1000);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd", Locale.CHINA);
Log.i(TAG, "expire time: " + sdf.format(date));
if (objExpireTime.getExpireTime() * 1000 > System.currentTimeMillis()) {
// 已经成功获取了授权,并且距离授权到期有充足的时间(>7天)
Log.i(TAG, "checkAuth success");
return initResult;
}
}
// 获取过期时间失败或者已经过期
initResult = HciCloudSys.hciCheckAuth();
if (initResult == HciErrorCode.HCI_ERR_NONE) {
Log.i(TAG, "checkAuth success");
return initResult;
} else {
Log.e(TAG, "checkAuth failed: " + initResult);
return initResult;
}
}
}

封装灵云语音合成能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
package kong.qingwei.kqwhcittsdemo;
import android.app.Activity;
import android.os.Environment;
import android.util.Log;
import android.widget.Toast;
import com.sinovoice.hcicloudsdk.api.HciCloudSys;
import com.sinovoice.hcicloudsdk.common.AuthExpireTime;
import com.sinovoice.hcicloudsdk.common.HciErrorCode;
import com.sinovoice.hcicloudsdk.common.InitParam;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
/**
* Created by kqw on 2016/8/12.
* 初始化灵云语音
*/
public class HciUtil {
private static final String TAG = "HciUtil";
private Activity mActivity;
private final String mConfigStr;
public HciUtil(Activity activity) {
mActivity = activity;
// 加载信息,返回InitParam, 获得配置参数的字符串
InitParam initParam = getInitParam();
mConfigStr = initParam.getStringConfig();
}
public boolean initHci() {
// 初始化
int errCode = HciCloudSys.hciInit(mConfigStr, mActivity);
if (errCode != HciErrorCode.HCI_ERR_NONE && errCode != HciErrorCode.HCI_ERR_SYS_ALREADY_INIT) {
Toast.makeText(mActivity, "hciInit error: " + HciCloudSys.hciGetErrorInfo(errCode), Toast.LENGTH_SHORT).show();
return false;
}
// 获取授权/更新授权文件 :
errCode = checkAuthAndUpdateAuth();
if (errCode != HciErrorCode.HCI_ERR_NONE) {
// 由于系统已经初始化成功,在结束前需要调用方法hciRelease()进行系统的反初始化
Toast.makeText(mActivity, "CheckAuthAndUpdateAuth error: " + HciCloudSys.hciGetErrorInfo(errCode), Toast.LENGTH_SHORT).show();
HciCloudSys.hciRelease();
return false;
}
return true;
}
/**
* 加载初始化信息
*
* @return 系统初始化参数
*/
private InitParam getInitParam() {
String authDirPath = mActivity.getFilesDir().getAbsolutePath();
// 前置条件:无
InitParam initparam = new InitParam();
// 授权文件所在路径,此项必填
initparam.addParam(InitParam.AuthParam.PARAM_KEY_AUTH_PATH, authDirPath);
// 是否自动访问云授权,详见 获取授权/更新授权文件处注释
initparam.addParam(InitParam.AuthParam.PARAM_KEY_AUTO_CLOUD_AUTH, "no");
// 灵云云服务的接口地址,此项必填
initparam.addParam(InitParam.AuthParam.PARAM_KEY_CLOUD_URL, ConfigUtil.CLOUD_URL);
// 开发者Key,此项必填,由捷通华声提供
initparam.addParam(InitParam.AuthParam.PARAM_KEY_DEVELOPER_KEY, ConfigUtil.DEVELOPER_KEY);
// 应用Key,此项必填,由捷通华声提供
initparam.addParam(InitParam.AuthParam.PARAM_KEY_APP_KEY, ConfigUtil.APP_KEY);
// 配置日志参数
String sdcardState = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(sdcardState)) {
String sdPath = Environment.getExternalStorageDirectory().getAbsolutePath();
String packageName = mActivity.getPackageName();
String logPath = sdPath + File.separator + "sinovoice" + File.separator + packageName + File.separator + "log" + File.separator;
// 日志文件地址
File fileDir = new File(logPath);
if (!fileDir.exists()) {
fileDir.mkdirs();
}
// 日志的路径,可选,如果不传或者为空则不生成日志
initparam.addParam(InitParam.LogParam.PARAM_KEY_LOG_FILE_PATH, logPath);
// 日志数目,默认保留多少个日志文件,超过则覆盖最旧的日志
initparam.addParam(InitParam.LogParam.PARAM_KEY_LOG_FILE_COUNT, "5");
// 日志大小,默认一个日志文件写多大,单位为K
initparam.addParam(InitParam.LogParam.PARAM_KEY_LOG_FILE_SIZE, "1024");
// 日志等级,0=无,1=错误,2=警告,3=信息,4=细节,5=调试,SDK将输出小于等于logLevel的日志信息
initparam.addParam(InitParam.LogParam.PARAM_KEY_LOG_LEVEL, "5");
}
return initparam;
}
/**
* 获取授权
*
* @return 授权结果
*/
private int checkAuthAndUpdateAuth() {
// 获取系统授权到期时间
int initResult;
AuthExpireTime objExpireTime = new AuthExpireTime();
initResult = HciCloudSys.hciGetAuthExpireTime(objExpireTime);
if (initResult == HciErrorCode.HCI_ERR_NONE) {
// 显示授权日期,如用户不需要关注该值,此处代码可忽略
Date date = new Date(objExpireTime.getExpireTime() * 1000);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd", Locale.CHINA);
Log.i(TAG, "expire time: " + sdf.format(date));
if (objExpireTime.getExpireTime() * 1000 > System.currentTimeMillis()) {
// 已经成功获取了授权,并且距离授权到期有充足的时间(>7天)
Log.i(TAG, "checkAuth success");
return initResult;
}
}
// 获取过期时间失败或者已经过期
initResult = HciCloudSys.hciCheckAuth();
if (initResult == HciErrorCode.HCI_ERR_NONE) {
Log.i(TAG, "checkAuth success");
return initResult;
} else {
Log.e(TAG, "checkAuth failed: " + initResult);
return initResult;
}
}
}

使用

初始化

1
2
3
4
5
6
7
8
9
10
// 灵云语音工具类
HciUtil mInitTts = new HciUtil(this);
// 初始化灵云语音
boolean isInitHci = mInitTts.initHci();
if (isInitHci) { // 初始化成功
// 语音合成能力工具类
mTtsUtil = new TtsUtil(this);
// 初始化语音合成
isInitPlayer = mTtsUtil.initPlayer(this);
}

语音合成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* 开始合成
*
* @param view v
*/
public void synth(View view) {
if (!isInitPlayer) {
Toast.makeText(this, "初始化失败", Toast.LENGTH_SHORT).show();
return;
}
String text = mEditText.getText().toString();
if (TextUtils.isEmpty(text)) {
Toast.makeText(this, "合成内容为空", Toast.LENGTH_SHORT).show();
return;
}
mTtsUtil.synth(text);
}

语音合成回调

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 语音合成状态的回调
@Override
public void onPlayerEventStateChange(TTSCommonPlayer.PlayerEvent playerEvent) {
Log.i(TAG, "onStateChange " + playerEvent.name());
}
// 合成进度回调
@Override
public void onPlayerEventProgressChange(TTSCommonPlayer.PlayerEvent playerEvent, int start, int end) {
Log.i(TAG, "onProcessChange " + playerEvent.name() + " from " + start + " to " + end);
}
// 错误回调
@Override
public void onPlayerEventPlayerError(TTSCommonPlayer.PlayerEvent playerEvent, int errorCode) {
Log.i(TAG, "onError " + playerEvent.name() + " code: " + errorCode);
}

灵云离线语音合成

离线语音合成相对也比较简单,首先要下载离线资源,下载完以后是一个zip包,解压缩里面有类似这样的几个文件

下载离线资源文件

给每个文件重命名,前面加lib,后面加后缀.so,然后导入工程

重命名

导入资源文件以后,修改capKey为离线语音合成

1
public static final String CAP_KEY = "tts.local.synth";

注:灵云的离线语音能力,第一次使用的时候也是需要有一个联网授权的过程,授权成功以后,即可在授权期内使用离线语音能力。

Android连接WIFI

发表于 2016-08-04 |

转载请说明出处!
作者:kqw攻城狮
出处:个人站 | CSDN


效果图

打开WIFI并获取WIFI列表

打开WIFI并获取WIFI列表

连接到指定WIFI

连接到指定WIFI

直接连接配置过的WIFI

直接连接配置过的WIFI

密码错误

密码错误

源码

KqwWifiManagerDemo

WIFI的获取、连接状态等等的信息,都是通过广播获取的.

下面介绍了主要的方法,更多请查看KqwWifiManager

注册广播接收者

1
2
3
4
5
6
7
8
9
10
11
<!-- 监听网络状态的广播接收者 -->
<receiver android:name=".KqwWifiManager$NetworkBroadcastReceiver">
<intent-filter>
<!-- AP扫描完成,客户端得到可用的结果集 -->
<action android:name="android.net.wifi.SCAN_RESULTS" />
<action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
<action android:name="android.net.wifi.WIFI_STATE_CHANGED" />
<action android:name="android.net.wifi.STATE_CHANGE" />
<action android:name="android.net.wifi.supplicant.STATE_CHANGE" />
</intent-filter>
</receiver>

广播接收者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
/**
* 广播接收者
*/
public static class NetworkBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
try {
String action = intent.getAction();
Log.i(TAG, "onReceive: action = " + action);
// 监听WIFI的启用状态
if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(action)) {
int wifiState = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE, 0);
switch (wifiState) {
case WifiManager.WIFI_STATE_ENABLING:
Log.i(TAG, "onReceive: 正在打开 WIFI...");
break;
case WifiManager.WIFI_STATE_ENABLED:
Log.i(TAG, "onReceive: WIFI 已打开");
if (null != mOnWifiEnabledListener) {
mOnWifiEnabledListener.onWifiEnabled(true);
mOnWifiEnabledListener.onFinish();
}
break;
case WifiManager.WIFI_STATE_DISABLING:
Log.i(TAG, "onReceive: 正在关闭 WIFI...");
break;
case WifiManager.WIFI_STATE_DISABLED:
Log.i(TAG, "onReceive: WIFI 已关闭");
if (null != mOnWifiEnabledListener) {
mOnWifiEnabledListener.onWifiEnabled(false);
mOnWifiEnabledListener.onFinish();
}
break;
case WifiManager.WIFI_STATE_UNKNOWN:
Log.i(TAG, "onReceive: WIFI 状态未知!");
break;
default:
break;
}
}
// WIFI扫描完成
if (WifiManager.SCAN_RESULTS_AVAILABLE_ACTION.equals(action)) {
if (null != mOnWifiScanResultsListener) {
mOnWifiScanResultsListener.onScanResults(getScanResults());
mOnWifiScanResultsListener.onFinish();
}
}
// WIFI 连接状态的监听(只有WIFI可用的时候,监听才会有效)
if (WifiManager.NETWORK_STATE_CHANGED_ACTION.equals(action)) {
NetworkInfo networkInfo = intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
if (null != networkInfo && networkInfo.isConnected()) {
WifiInfo wifiInfo = intent.getParcelableExtra(WifiManager.EXTRA_WIFI_INFO);
if (null != wifiInfo && String.format("\"%s\"", mConnectingSSID).equals(wifiInfo.getSSID()) && null != mOnWifiConnectListener) {
// WIFI连接成功
mOnWifiConnectListener.onSuccess(wifiInfo.getSSID());
mOnWifiConnectListener.onFinish();
mOnWifiConnectListener = null;
}
}
}
// WIFI连接过程的监听
if (intent.getAction().equals(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION)) {
WifiInfo wifiInfo = mWifiManager.getConnectionInfo();
SupplicantState state = wifiInfo.getSupplicantState();
switch (state) {
case INTERFACE_DISABLED: // 接口禁用
Log.i(TAG, "onReceive: INTERFACE_DISABLED 接口禁用");
break;
case DISCONNECTED:// 断开连接
case INACTIVE: // 不活跃的
Log.i(TAG, "onReceive: INACTIVE 不活跃的 DISCONNECTED:// 断开连接");
if (null != mOnWifiConnectListener) {
// 断开当前网络失败
mOnWifiConnectListener.onFailure();
// 连接完成
mOnWifiConnectListener.onFinish();
mOnWifiConnectListener = null;
mConnectingSSID = null;
}
break;
case SCANNING: // 正在扫描
Log.i(TAG, "onReceive: SCANNING 正在扫描");
break;
case AUTHENTICATING: // 正在验证
Log.i(TAG, "onReceive: AUTHENTICATING: // 正在验证");
if (null != mOnWifiConnectListener) {
mOnWifiConnectListener.onConnectingMessage("正在验证");
}
break;
case ASSOCIATING: // 正在关联
Log.i(TAG, "onReceive: ASSOCIATING: // 正在关联");
if (null != mOnWifiConnectListener) {
mOnWifiConnectListener.onConnectingMessage("正在关联");
}
break;
case ASSOCIATED: // 已经关联
Log.i(TAG, "onReceive: ASSOCIATED: // 已经关联");
if (null != mOnWifiConnectListener) {
mOnWifiConnectListener.onConnectingMessage("已经关联");
}
break;
case FOUR_WAY_HANDSHAKE:
Log.i(TAG, "onReceive: FOUR_WAY_HANDSHAKE:");
break;
case GROUP_HANDSHAKE:
Log.i(TAG, "onReceive: GROUP_HANDSHAKE:");
break;
case COMPLETED: // 完成
Log.i(TAG, "onReceive: COMPLETED: // 完成");
if (null != mOnWifiConnectListener) {
mOnWifiConnectListener.onConnectingMessage("正在连接...");
}
break;
case DORMANT:
Log.i(TAG, "onReceive: DORMANT:");
break;
case UNINITIALIZED: // 未初始化
Log.i(TAG, "onReceive: UNINITIALIZED: // 未初始化");
break;
case INVALID: // 无效的
Log.i(TAG, "onReceive: INVALID: // 无效的");
break;
default:
break;
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}

权限

1
2
3
4
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

打开WIFI

1
2
3
4
5
6
7
8
9
10
/**
* 打开Wifi
*/
public void openWifi(@NonNull OnWifiEnabledListener listener) {
if (!mWifiManager.isWifiEnabled()) {
mOnWifiEnabledListener = listener;
mOnWifiEnabledListener.onStart(true);
mWifiManager.setWifiEnabled(true);
}
}

关闭WIFI

1
2
3
4
5
6
7
8
9
10
/**
* 关闭Wifi
*/
public void closeWifi(@NonNull OnWifiEnabledListener listener) {
if (mWifiManager.isWifiEnabled()) {
mOnWifiEnabledListener = listener;
mOnWifiEnabledListener.onStart(false);
mWifiManager.setWifiEnabled(false);
}
}

扫描附近的WIFI

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* 扫描附近的WIFI
*
* @param listener 扫描完成的回调接口
*/
public void startScan(@NonNull OnWifiScanResultsListener listener) {
try {
mOnWifiScanResultsListener = listener;
mOnWifiScanResultsListener.onStart();
// 先返回缓存
mOnWifiScanResultsListener.onScanResults(getScanResults());
// 重新开始扫描
mWifiManager.startScan();
} catch (Exception e) {
e.printStackTrace();
}
}

获取WIFI列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 获取Wifi列表
*
* @return Wifi列表
*/
private static List<ScanResult> getScanResults() {
try {
// 得到扫描结果
return mWifiManager.getScanResults();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}

通过密码连接到WIFI

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/**
* 通过密码连接到WIFI
*
* @param scanResult 要连接的WIFI
* @param pwd 密码
* @param listener 连接的监听
*/
public void connectionWifiByPassword(@NonNull ScanResult scanResult, @Nullable String pwd, @NonNull OnWifiConnectListener listener) {
// SSID
String SSID = scanResult.SSID;
// 加密方式
SecurityMode securityMode = getSecurityMode(scanResult);
// 生成配置文件
WifiConfiguration addConfig = createWifiConfiguration(SSID, pwd, securityMode);
int netId;
// 判断当前配置是否存在
WifiConfiguration updateConfig = isExists(addConfig);
if (null != updateConfig) {
// 更新配置
netId = mWifiManager.updateNetwork(updateConfig);
} else {
// 添加配置
netId = mWifiManager.addNetwork(addConfig);
}
// 通过NetworkID连接到WIFI
connectionWifiByNetworkId(SSID, netId, listener);
}

直接连接配置过的WIFI

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
/**
* 通过NetworkId连接到WIFI (配置过的网络可以直接获取到NetworkID,从而不用再输入密码)
*
* @param SSID WIFI名字
* @param networkId NetworkId
* @param listener 连接的监听
*/
public void connectionWifiByNetworkId(@NonNull String SSID, int networkId, @NonNull OnWifiConnectListener listener) {
// 正要连接的SSID
mConnectingSSID = SSID;
// 连接的回调监听
mOnWifiConnectListener = listener;
// 连接开始的回调
mOnWifiConnectListener.onStart(SSID);
/*
* 判断 NetworkId 是否有效
* -1 表示配置参数不正确
*/
if (-1 == networkId) {
// 连接WIFI失败
if (null != mOnWifiConnectListener) {
// 配置错误
mOnWifiConnectListener.onFailure();
// 连接完成
mOnWifiConnectListener.onFinish();
mOnWifiConnectListener = null;
mConnectingSSID = null;
}
return;
}
// 获取当前的网络连接
WifiInfo wifiInfo = getConnectionInfo();
if (null != wifiInfo) {
// 断开当前连接
boolean isDisconnect = disconnectWifi(wifiInfo.getNetworkId());
if (!isDisconnect) {
// 断开当前网络失败
if (null != mOnWifiConnectListener) {
// 断开当前网络失败
mOnWifiConnectListener.onFailure();
// 连接完成
mOnWifiConnectListener.onFinish();
mOnWifiConnectListener = null;
mConnectingSSID = null;
}
return;
}
}
// 连接WIFI
boolean isEnable = mWifiManager.enableNetwork(networkId, true);
if (!isEnable) {
// 连接失败
if (null != mOnWifiConnectListener) {
// 连接失败
mOnWifiConnectListener.onFailure();
// 连接完成
mOnWifiConnectListener.onFinish();
mOnWifiConnectListener = null;
mConnectingSSID = null;
}
}
}

断开指定WIFI

1
2
3
4
5
6
7
8
9
10
11
/**
* 断开WIFI
*
* @param netId netId
* @return 是否断开
*/
public boolean disconnectWifi(int netId) {
boolean isDisable = mWifiManager.disableNetwork(netId);
boolean isDisconnect = mWifiManager.disconnect();
return isDisable && isDisconnect;
}

删除配置

1
2
3
4
5
6
7
8
9
10
11
12
/**
* 删除配置
*
* @param netId netId
* @return 是否删除成功
*/
private boolean deleteConfig(int netId) {
boolean isDisable = mWifiManager.disableNetwork(netId);
boolean isRemove = mWifiManager.removeNetwork(netId);
boolean isSave = mWifiManager.saveConfiguration();
return isDisable && isRemove && isSave;
}

生成配置信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
/**
* 生成新的配置信息 用于连接Wifi
*
* @param SSID WIFI名字
* @param password WIFI密码
* @param mode WIFI加密类型
* @return 配置
*/
private WifiConfiguration createWifiConfiguration(@NonNull String SSID, @Nullable String password, @NonNull SecurityMode mode) {
WifiConfiguration config = new WifiConfiguration();
config.allowedAuthAlgorithms.clear();
config.allowedGroupCiphers.clear();
config.allowedKeyManagement.clear();
config.allowedPairwiseCiphers.clear();
config.allowedProtocols.clear();
config.SSID = "\"" + SSID + "\"";
if (mode == SecurityMode.OPEN) {
//WIFICIPHER_NOPASS
// config.wepKeys[0] = "";
config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
// config.wepTxKeyIndex = 0;
} else if (mode == SecurityMode.WEP) {
//WIFICIPHER_WEP
config.hiddenSSID = true;
config.wepKeys[0] = "\"" + password + "\"";
config.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.SHARED);
config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP);
config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.TKIP);
config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.WEP40);
config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.WEP104);
config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
config.wepTxKeyIndex = 0;
} else if (mode == SecurityMode.WPA) {
//WIFICIPHER_WPA
config.preSharedKey = "\"" + password + "\"";
config.hiddenSSID = true;
config.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.OPEN);
config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.TKIP);
config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
config.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.TKIP);
//config.allowedProtocols.set(WifiConfiguration.Protocol.WPA);
config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP);
config.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP);
config.status = WifiConfiguration.Status.ENABLED;
}
return config;
}

目前存在的问题

目前存在的问题是,当删除配置以后,再连接WIFI,调用addNetwork或者updateNetwork都会返回-1,需要重启一下WIFI才会正常,有知道原因还请指点一下。

Android蓝牙通信

发表于 2016-08-02 |

转载请说明出处!
作者:kqw攻城狮
出处:个人站 | CSDN


Android蓝牙通信

效果图

两台真机设备

G1

源码

GitHub

  • 关于蓝牙的开关控制,设置设备可见、搜索附近的蓝牙设备,已经封装到了 BluetoothManager
    类

  • 关于设备的连接、通信。已经封装到了 BluetoothService 类

注:下面的全部内容,主要是思路,具体的可以参考上面的源码,如果对你有帮助记得给个赞哦。

权限

1
2
3
<!-- 蓝牙的权限 -->
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />

蓝牙的打开与关闭

开启蓝牙

1
2
3
4
5
6
7
8
9
10
11
12
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
/**
* 开启蓝牙
*/
public void openBluetooth() {
try {
mBluetoothAdapter.enable();
} catch (Exception e) {
e.printStackTrace();
}
}

关闭蓝牙

1
2
3
4
5
6
7
8
9
10
11
12
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
/**
* 关闭蓝牙
*/
public void closeBluetooth() {
try {
mBluetoothAdapter.disable();
} catch (Exception e) {
e.printStackTrace();
}
}

设置蓝牙设备可见

设置设备可见对于服务端是必须的,客户端设不设置无所谓。

如果服务端不可见,配对过的设备也搜索到并可以连接上,但是不能通信,没有配对过的设备连搜索都搜索不到。

可见时间的取值范围是0到120,单位是秒,0表示永久可见。
手机上的蓝牙可见仅限一次连接有效。也就是说,一次连接断开以后,下次再等待客户端连接的时候,还需要再设置一次设备可见。

1
2
3
4
5
6
7
8
9
/**
* 设置设备可见
* 0 ~ 120
*/
public void setDuration() {
Intent duration = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
duration.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 120);
mActivity.startActivity(duration);
}

扫描附近的蓝牙设备

扫描附近设备的设备,需要注册一个广播接收者,来接收扫描到的结果。

需要注意的是,接收扫描结果的广播接收者必须使用动态注册,不能在清单文件里注册!

注册搜索蓝牙设备的广播接收者

1
2
3
4
5
6
7
8
9
10
11
12
// 获取设备的广播接收者
FoundDeviceBroadcastReceiver mFoundDeviceBroadcastReceiver = new FoundDeviceBroadcastReceiver();
// 注册receiver监听
IntentFilter mFilter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
/**
* 注册搜索蓝牙设备的广播接收者
*/
public void registerFoundDeviceReceiver() {
mActivity.registerReceiver(mFoundDeviceBroadcastReceiver, mFilter);
}

反注册搜索蓝牙设备的广播接收者

1
2
3
4
5
6
7
8
9
// 获取设备的广播接收者
FoundDeviceBroadcastReceiver mFoundDeviceBroadcastReceiver = new FoundDeviceBroadcastReceiver();
/**
* 反注册搜索蓝牙设备的广播接收者
*/
public void unregisterReceiver() {
mActivity.unregisterReceiver(mFoundDeviceBroadcastReceiver);
}

广播接收者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/**
* Created by kqw on 2016/8/2.
* 蓝牙的广播接收者
*/
public class FoundDeviceBroadcastReceiver extends BroadcastReceiver {
private static final String TAG = "FoundDeviceBroadcast";
private static OnFoundDeviceListener mOnFoundDeviceListener;
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
// 获取设备
BluetoothDevice btDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
if (BluetoothDevice.ACTION_FOUND.equals(action)) {
// 扫描发现的设备
if (null != mOnFoundDeviceListener) {
mOnFoundDeviceListener.foundDevice(btDevice);
}
}
……
}
public void setOnFoundDeviceListener(OnFoundDeviceListener listener) {
mOnFoundDeviceListener = listener;
}
}

开始扫描附近的蓝牙设备

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 开始扫描设备
*/
public void startDiscovery() {
Log.i(TAG, "startDiscovery: ");
if (mBluetoothAdapter.isDiscovering()) {
mBluetoothAdapter.cancelDiscovery();
} else {
// TODO 这里可以先获取已经配对的设备
// Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices();
// 开始扫描设备
mBluetoothAdapter.startDiscovery();
}
}

获取已经配对的设备

扫描附近的蓝牙设备是一个很消耗性能的操作,在扫描之前,可以先获取已经配对过的设备,如果已经配对过,就不用再扫描了。

1
2
BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices();

蓝牙连接

获取到附近的设备以后,就可以通过MAC地址进行配对连接了。

配对

没有配对过的设备,在连接之前是需要配对的,配对成功才可以连接、通信。

配对可以手动点击,根据配对码进行配对,也可以设置自动配对。手动配对没什么好说的,这里介绍自动配对

还是用到上面的蓝牙广播接收者,我们在清单文件里添加Action

1
2
3
4
5
6
7
<!-- 蓝牙广播接收者 -->
<receiver android:name=".receiver.FoundDeviceBroadcastReceiver">
<intent-filter>
<!-- 添加配对请求 -->
<action android:name="android.bluetooth.device.action.PAIRING_REQUEST" />
</intent-filter>
</receiver>

注意:这里的自动配对仅支持4.4.2以上系统,以下的版本想要实现需要用到反射

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/**
* Created by kqw on 2016/8/2.
* 蓝牙的广播接收者
*/
public class FoundDeviceBroadcastReceiver extends BroadcastReceiver {
private static final String TAG = "FoundDeviceBroadcast";
private static OnFoundDeviceListener mOnFoundDeviceListener;
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
// 获取设备
BluetoothDevice btDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
if (BluetoothDevice.ACTION_PAIRING_REQUEST.equals(action)) {
if (new ConfigUtil(context).getPairingConfirmation()) {
// 收到配对请求
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
// 同意请求
btDevice.setPairingConfirmation(true);
} else {
Log.i(TAG, "onReceive: 4.4.2 以下版本的设备需要通过反射实现自动配对");
}
}
}
……
}
}

连接

服务端等待连接

服务端开启连接,需要开启一个阻塞线程,等待客户端的连接,类似这样

1
2
3
4
5
6
7
try {
// 等待客户端连接 阻塞线程 连接成功继续向下执行 连接失败抛异常
socket = mmServerSocket.accept();
} catch (IOException e) {
Log.e(TAG, "Socket Type: " + mSocketType + "accept() failed", e);
break;
}

客户端发起连接

客户端发起连接,如果没有配对过,需要先进行配对,连接同样是一个阻塞线程,连接成功会继续向下执行,连接失败会抛异常,类似这样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
try {
……
// 开始连接 阻塞线程 连接成功继续执行 连接失败抛异常
mmSocket.connect();
} catch (IOException e) {
// 连接失败
e.printStackTrace();
try {
mmSocket.close();
} catch (IOException e2) {
Log.e(TAG, "unable to close() " + mSocketType + " socket during connection failure", e2);
}
……
}

通信

接收数据

连接成功以后,需要开启一个线程,一直循环读取数据流,类似这样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 只有蓝牙处于连接状态就一直循环读取数据
while (mState == STATE_CONNECTED) {
try {
// Read from the InputStream
bytes = mmInStream.read(buffer);
// 读取到数据的回调
……
} catch (IOException e) {
// 读取数据出现异常
Log.e(TAG, "disconnected", e);
……
}
}

发送数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 发数据
*
* @param buffer 发送内容
*/
public void write(final byte[] buffer) {
try {
mmOutStream.write(buffer);
// 发送数据的回调
……
} catch (IOException e) {
// 发送数据出现失败
Log.e(TAG, "Exception during write", e);
}
}

坑

有时候当你重复的连接、断开、连接、断开……

你会发现出现连接失败,或者可以连接成功,但是不能通信了,这个时候你要考虑你得服务端是不是已经不可见了。

上面已经提过,如果两个设备已经配对,即使服务端是不可见的,也同样可以搜索到并连接上,但是是不能通信的。

而如果两个设备没有配对过,是连搜索都搜索不到服务端的。

当然,如果在服务端可见的时候,连接上就是连接上了,只要连接不断开,即使连接上以后服务端变为不可见了,也一样可以一直通信。

Android设备的唯一标识

发表于 2016-07-27 |

转载请说明出处!
作者:kqw攻城狮
出处:个人站 | CSDN


Android设备的唯一标识

IMEI

权限

1
<uses-permission android:name="android.permission.READ_PHONE_STATE" />

获取IMEI

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 获取IMEI
*
* @return IMEI
*/
private String getIMEI() {
try {
TelephonyManager TelephonyMgr = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
return TelephonyMgr.getDeviceId();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}

WLAN MAC Address

权限

1
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />

获取MAC地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 获取WLAN MAC Address
*
* @return MAC地址
*/
private String getWLANMacAddress() {
try {
WifiManager wifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE);
WifiInfo wifiInfo = wifiManager.getConnectionInfo();
return wifiInfo.getMacAddress();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}

BT MAC Address

权限

1
<uses-permission android:name="android.permission.BLUETOOTH" />

获取MAC地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 获取BT MAC Address
*
* @return MAC地址
*/
private String getBTMacAddress() {
try {
BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
return bluetoothAdapter.getAddress();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}

MD5加密

附加一个MD5的加密算法,考虑在某些特殊设备上可能获取不到某个ID,可以获取多个ID,组合起来,通过MD5算法,得到一个32位的唯一标识

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/**
* MD5加密
*
* @param text 要加密的字符串
* @return 加密后的32位结果
*/
private String digest(String text) {
try {
// 创建一个MD5加密算法
MessageDigest digest = MessageDigest.getInstance("MD5");
// 创建StringBuffer保存十六进制数据
StringBuffer sb = new StringBuffer();
// 换成字节数组
byte[] bytes = digest.digest(text.getBytes());
// 遍历字节数组加密
for (byte bt : bytes) {
// 将负数转换为正数
int i = bt & 0xff;
// 将十进制转换为十六进制
String hex = Integer.toHexString(i);
// 如果不够2位,前面补0
if (2 > hex.length()) {
sb.append(0);
}
sb.append(hex);
}
return sb.toString();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
return null;
}
}

例:

1
2
3
String text = getWLANMacAddress() + getBTMacAddress() + getIMEI();
String md5 = digest(text);
Toast.makeText(this, "MD5:" + md5, Toast.LENGTH_SHORT).show();

Android使用OpenCV实现人脸识别

发表于 2016-07-06 | 分类于 OpenCV |

转载请说明出处!
作者:kqw攻城狮
出处:个人站 | CSDN


效果图

先上效果图,GIF不好弄

人脸检测

人脸特征保存

人脸特征保存

人脸对比

在网上找了在Android平台上使用OpenCV相关的教程,很少,大部分也都主要是介绍下人脸检测,很少有讲人脸识别,还有的人连人脸检测和人脸识别的概念都没有搞清,人脸识别只是识别到有人脸,能获取到一个人脸的大概位置,有几个人脸,而人脸识别是要获取到人脸特征做对比,识别这个人脸。有好多文章都写自己在讲人脸识别,实际上他只是在做人脸检测。

OpenCV官网

官方给的Demo是在Eclipse工程下的,如果你现在已经是在Android Studio下开发,因为Eclipse工程有makefile文件,迁移到Android Studio好像还是有点麻烦,我是干脆就在Eclipse下跑的Demo。

先甩过来官方给的一些文档:

OpenCV4Android SDK

Android Development with OpenCV

实现方式

按照官方的文档,我们在Eclipse里导入Demo进去以后,是不能直接运行的,需要安装Manager的一个APK,然后在Demo工程里通过AIDL的方式,调用OpenCV的核心方法,不过Demo给实现的功能也只是一个人脸检测。

SDK

SDK下载

下面来看一下SDK

下载SDK

目录:

  • apk:Manager的apk
  • doc:一些文档
  • samples:示例工程和一些编译好的apk
  • sdk:一些库文件

当然, 如果你的C/C++足够好,你肯定可以自己编译一个库,直接导入到工程,就不用安装Manager了,可惜了我自己还不行,哈哈……无奈安装Manager把……

如何将Demo导入到Eclipse并运行,上面官方的文档已经说的比较清楚了,至于会有什么问题就自行Google吧。

人脸检测

其实人脸检测并不是重点,Demo里已经实现了人脸检测的功能。

主要的实现方式:OpenCV有一个自己的org.opencv.android.JavaCameraView自定义控件,它循环的从摄像头抓取数据,在回调方法中,我们能获取到Mat数据,然后通过调用OpenCV的Native方法,检测当前是否有人脸,我们会获取到一个Rect数组,里面会有人脸数据,最后将人脸画在屏幕上,到此为止,Demo的人脸检测功能,就结束了。

人脸识别

人脸识别我这里用到了JavaCV

JavaCV

人脸识别逻辑:人脸识别的主要方式就是获取到人脸的特征值,然后将两个特征值做比对,取到一个相似度去做人脸识别,OpenCV这里的特征值,其实就是一张图片。
我们的从回调的Mat数据检测到有人脸以后,提取特征值(也就是保存人脸的一张图片到某个路径),然后比较特征值

为了提高识别的准确度,需要在检测到人脸以后,把人脸的部分截取出来,然后置灰(置灰的目的是为了方式色泽和明暗度对识别有影响)。

保存人脸特征值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* 特征保存
*
* @param image Mat
* @param rect 人脸信息
* @param fileName 文件名字
* @return 保存是否成功
*/
public boolean saveImage(Mat image, Rect rect, String fileName) {
try {
String PATH = Environment.getExternalStorageDirectory() + "/FaceDetect/" + fileName + ".jpg";
// 把检测到的人脸重新定义大小后保存成文件
Mat sub = image.submat(rect);
Mat mat = new Mat();
Size size = new Size(100, 100);
Imgproc.resize(sub, mat, size);
Highgui.imwrite(PATH, mat);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

提取特征值

之前已经说了嘛,人脸特征其实就是一张人脸图片,所以提取人脸特征其实就是获取一张人脸图片

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 提取特征
*
* @param fileName 文件名
* @return 特征图片
*/
public Bitmap getImage(String fileName) {
try {
return BitmapFactory.decodeFile(Environment.getExternalStorageDirectory() + "/FaceDetect/" + fileName + ".jpg");
} catch (Exception e) {
e.printStackTrace();
return null;
}
}

人脸识别

这里主要使用了JavaCV的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/**
* 特征对比
*
* @param file1 人脸特征
* @param file2 人脸特征
* @return 相似度
*/
public double CmpPic(String file1, String file2) {
int l_bins = 20;
int hist_size[] = {l_bins};
float v_ranges[] = {0, 100};
float ranges[][] = {v_ranges};
opencv_core.IplImage Image1 = cvLoadImage(Environment.getExternalStorageDirectory() + "/FaceDetect/" + file1 + ".jpg", CV_LOAD_IMAGE_GRAYSCALE);
opencv_core.IplImage Image2 = cvLoadImage(Environment.getExternalStorageDirectory() + "/FaceDetect/" + file2 + ".jpg", CV_LOAD_IMAGE_GRAYSCALE);
opencv_core.IplImage imageArr1[] = {Image1};
opencv_core.IplImage imageArr2[] = {Image2};
opencv_imgproc.CvHistogram Histogram1 = opencv_imgproc.CvHistogram.create(1, hist_size, CV_HIST_ARRAY, ranges, 1);
opencv_imgproc.CvHistogram Histogram2 = opencv_imgproc.CvHistogram.create(1, hist_size, CV_HIST_ARRAY, ranges, 1);
cvCalcHist(imageArr1, Histogram1, 0, null);
cvCalcHist(imageArr2, Histogram2, 0, null);
cvNormalizeHist(Histogram1, 100.0);
cvNormalizeHist(Histogram2, 100.0);
return cvCompareHist(Histogram1, Histogram2, CV_COMP_CORREL);
}

最后

看我轻描淡写了一篇博客写完了,看上去好像挺容易的,但是对于第一次做人脸识别的人或者对JNI还不是很熟的人来说,可能并没有想象的那么简单,你会遇到各种库找不到,编译不通过等等的问题,总之吧,一个大概的实现思路呈现出来,仅供参考,就是这样!有时间的话会再深入学习一下。

Android服务

发表于 2016-04-15 |

转载请说明出处!
作者:kqw攻城狮
出处:个人站 | CSDN


服务

Service 是一个可以在后台执行长时间运行操作而不使用用户界面的应用组件。服务可由其他应用组件启动,而且即使用户切换到其他应用,服务仍将在后台继续运行。 此外,组件可以绑定到服务,以与之进行交互,甚至是执行进程间通信 (IPC)。 例如,服务可以处理网络事务、播放音乐,执行文件 I/O 或与内容提供程序交互,而所有这一切均可在后台进行。

服务基本上分为两种形式:

  • 启动

    当应用组件(如 Activity)通过调用 startService() 启动服务时,服务即处于“启动”状态。一旦启动,服务即可在后台无限期运行,即使启动服务的组件已被销毁也不受影响。 已启动的服务通常是执行单一操作,而且不会将结果返回给调用方。例如,它可能通过网络下载或上传文件。 操作完成后,服务会自行停止运行。

  • 绑定

    当应用组件通过调用 bindService() 绑定到服务时,服务即处于“绑定”状态。绑定服务提供了一个客户端-服务器接口,允许组件与服务进行交互、发送请求、获取结果,甚至是利用进程间通信 (IPC) 跨进程执行这些操作。 仅当与另一个应用组件绑定时,绑定服务才会运行。 多个组件可以同时绑定到该服务,但全部取消绑定后,该服务即会被销毁。

虽然本文档是分开概括讨论这两种服务,但是您的服务可以同时以这两种方式运行,也就是说,它既可以是启动服务(以无限期运行),也允许绑定。问题只是在于您是否实现了一组回调方法:onStartCommand()(允许组件启动服务)和 onBind()(允许绑定服务)。

无论应用是处于启动状态还是绑定状态,抑或处于启动并且绑定状态,任何应用组件均可像使用活动那样通过调用 Intent 来使用服务(即使此服务来自另一应用)。 不过,您可以通过清单文件将服务声明为私有服务,并阻止其他应用访问。 使用清单文件声明服务部分将对此做更详尽的阐述。

  • 注意:

    服务在其托管进程的主线程中运行,它既不创建自己的线程,也不在单独的进程中运行(除非另行指定)。 这意味着,如果服务将执行任何 CPU 密集型工作或阻止性操作(例如 MP3 播放或联网),则应在服务内创建新线程来完成这项工作。通过使用单独的线程,可以降低发生“应用无响应”(ANR) 错误的风险,而应用的主线程仍可继续专注于运行用户与 Activity 之间的交互。

基础知识

要创建服务,您必须创建 Service 的子类(或使用它的一个现有子类)。在实现中,您需要重写一些回调方法,以处理服务生命周期的某些关键方面并提供一种机制将组件绑定到服务(如适用)。 应重写的最重要的回调方法包括:

  • onStartCommand()

    当另一个组件(如 Activity)通过调用 startService() 请求启动服务时,系统将调用此方法。一旦执行此方法,服务即会启动并可在后台无限期运行。 如果您实现此方法,则在服务工作完成后,需要由您通过调用 stopSelf() 或 stopService() 来停止服务。(如果您只想提供绑定,则无需实现此方法。)

  • onBind()

    当另一个组件想通过调用 bindService() 与服务绑定(例如执行 RPC)时,系统将调用此方法。在此方法的实现中,您必须通过返回 IBinder 提供一个接口,供客户端用来与服务进行通信。请务必实现此方法,但如果您并不希望允许绑定,则应返回 null。

  • onCreate()

    首次创建服务时,系统将调用此方法来执行一次性设置程序(在调用 onStartCommand() 或 onBind() 之前)。如果服务已在运行,则不会调用此方法。

  • onDestroy()

    当服务不再使用且将被销毁时,系统将调用此方法。服务应该实现此方法来清理所有资源,如线程、注册的侦听器、接收器等。 这是服务接收的最后一个调用。

如果组件通过调用 startService() 启动服务(这会导致对 onStartCommand() 的调用),则服务将一直运行,直到服务使用 stopSelf() 自行停止运行,或由其他组件通过调用 stopService() 停止它为止。

如果组件是通过调用 bindService() 来创建服务(且未调用 onStartCommand()),则服务只会在该组件与其绑定时运行。一旦该服务与所有客户端之间的绑定全部取消,系统便会销毁它。

仅当内存过低且必须回收系统资源以供具有用户焦点的 Activity 使用时,Android 系统才会强制停止服务。如果将服务绑定到具有用户焦点的 Activity,则它不太可能会终止;如果将服务声明为在前台运行(稍后讨论),则它几乎永远不会终止。或者,如果服务已启动并要长时间运行,则系统会随着时间的推移降低服务在后台任务列表中的位置,而服务也将随之变得非常容易被终止;如果服务是启动服务,则您必须将其设计为能够妥善处理系统对它的重启。 如果系统终止服务,那么一旦资源变得再次可用,系统便会重启服务(不过这还取决于从 onStartCommand() 返回的值,本文稍后会对此加以讨论)。如需了解有关系统会在何时销毁服务的详细信息,请参阅进程和线程文档。

在下文中,您将了解如何创建各类服务以及如何从其他应用组件使用服务。

使用清单文件声明服务

如同 Activity(以及其他组件)一样,您必须在应用的清单文件中声明所有服务。

要声明服务,请添加 元素作为 元素的子元素。例如:

1
2
3
4
5
6
7
<manifest ... >
...
<application ... >
<service android:name=".ExampleService" />
...
</application>
</manifest>

如需了解有关使用清单文件声明服务的详细信息,请参阅 元素参考文档。

您还可将其他属性包括在 元素中,以定义一些特性,如启动服务及其运行所在进程所需的权限。android:name 属性是唯一必需的属性,用于指定服务的类名。应用一旦发布,即不应更改此类名,如若不然,可能会存在因依赖显式 Intent 启动或绑定服务而破坏代码的风险(请阅读博客文章Things That Cannot Change[不能更改的内容])。

为了确保应用的安全性,请始终使用显式 Intent 启动或绑定 Service,且不要为服务声明 Intent 过滤器。 启动哪个服务存在一定的不确定性,而如果对这种不确定性的考量非常有必要,则可为服务提供 Intent 过滤器并从 Intent 中排除相应的组件名称,但随后必须使用 setPackage() 方法设置 Intent 的软件包,这样可以充分消除目标服务的不确定性。

此外,还可以通过添加 android:exported 属性并将其设置为 “false”,确保服务仅适用于您的应用。这可以有效阻止其他应用启动您的服务,即便在使用显式 Intent 时也如此。

创建启动服务

启动服务由另一个组件通过调用 startService() 启动,这会导致调用服务的 onStartCommand() 方法。

服务启动之后,其生命周期即独立于启动它的组件,并且可以在后台无限期地运行,即使启动服务的组件已被销毁也不受影响。 因此,服务应通过调用 stopSelf() 结束工作来自行停止运行,或者由另一个组件通过调用 stopService() 来停止它。

应用组件(如 Activity)可以通过调用 startService() 方法并传递 Intent 对象 (指定服务并包含待使用服务的所有数据)来启动服务。服务通过 onStartCommand() 方法接收此 Intent。

例如,假设某 Activity 需要将一些数据保存到在线数据库中。该 Activity 可以启动一个协同服务,并通过向 startService() 传递一个 Intent,为该服务提供要保存的数据。服务通过 onStartCommand() 接收 Intent,连接到 Internet 并执行数据库事务。事务完成之后,服务会自行停止运行并随即被销毁。

  • 注意:

    默认情况下,服务与服务声明所在的应用运行于同一进程,而且运行于该应用的主线程中。 因此,如果服务在用户与来自同一应用的 Activity 进行交互时执行密集型或阻止性操作,则会降低 Activity 性能。 为了避免影响应用性能,您应在服务内启动新线程。

从传统上讲,您可以扩展两个类来创建启动服务:

  • Service

    这是适用于所有服务的基类。扩展此类时,必须创建一个用于执行所有服务工作的新线程,因为默认情况下,服务将使用应用的主线程,这会降低应用正在运行的所有 Activity 的性能。

  • IntentService

    这是 Service 的子类,它使用工作线程逐一处理所有启动请求。如果您不要求服务同时处理多个请求,这是最好的选择。 您只需实现 onHandleIntent() 方法即可,该方法会接收每个启动请求的 Intent,使您能够执行后台工作。

下文介绍如何使用其中任一类实现服务。

扩展 IntentService 类

由于大多数启动服务都不必同时处理多个请求(实际上,这种多线程情况可能很危险),因此使用 IntentService 类实现服务也许是最好的选择。

IntentService 执行以下操作:

  • 创建默认的工作线程,用于在应用的主线程外执行传递给 onStartCommand() 的所有 Intent。
  • 创建工作队列,用于将一个 Intent 逐一传递给 onHandleIntent() 实现,这样您就永远不必担心多线程问题。
  • 在处理完所有启动请求后停止服务,因此您永远不必调用 stopSelf()。
  • 提供 onBind() 的默认实现(返回 null)。
  • 提供 onStartCommand() 的默认实现,可将 Intent 依次发送到工作队列和 onHandleIntent() 实现。

综上所述,您只需实现 onHandleIntent() 来完成客户端提供的工作即可。(不过,您还需要为服务提供小型构造函数。)

以下是 IntentService 的实现示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class HelloIntentService extends IntentService {
/**
* A constructor is required, and must call the super IntentService(String)
* constructor with a name for the worker thread.
*/
public HelloIntentService() {
super("HelloIntentService");
}
/**
* The IntentService calls this method from the default worker thread with
* the intent that started the service. When this method returns, IntentService
* stops the service, as appropriate.
*/
@Override
protected void onHandleIntent(Intent intent) {
// Normally we would do some work here, like download a file.
// For our sample, we just sleep for 5 seconds.
long endTime = System.currentTimeMillis() + 5*1000;
while (System.currentTimeMillis() < endTime) {
synchronized (this) {
try {
wait(endTime - System.currentTimeMillis());
} catch (Exception e) {
}
}
}
}
}

您只需要一个构造函数和一个 onHandleIntent() 实现即可。

如果您决定还重写其他回调方法(如 onCreate()、onStartCommand() 或 onDestroy()),请确保调用超类实现,以便 IntentService 能够妥善处理工作线程的生命周期。

例如,onStartCommand() 必须返回默认实现(即,如何将 Intent 传递给 onHandleIntent()):

1
2
3
4
5
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();
return super.onStartCommand(intent,flags,startId);
}

除 onHandleIntent() 之外,您无需从中调用超类的唯一方法就是 onBind()(仅当服务允许绑定时,才需要实现该方法)。

在下一部分中,您将了解如何在扩展 Service 基类时实现同类服务。该基类包含更多代码,但如需同时处理多个启动请求,则更适合使用该基类。

扩展服务类

正如上一部分中所述,使用 IntentService 显著简化了启动服务的实现。但是,若要求服务执行多线程(而不是通过工作队列处理启动请求),则可扩展 Service 类来处理每个 Intent。

为了便于比较,以下提供了 Service 类实现的代码示例,该类执行的工作与上述使用 IntentService 的示例完全相同。也就是说,对于每个启动请求,它均使用工作线程执行作业,且每次仅处理一个请求。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
public class HelloService extends Service {
private Looper mServiceLooper;
private ServiceHandler mServiceHandler;
// Handler that receives messages from the thread
private final class ServiceHandler extends Handler {
public ServiceHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
// Normally we would do some work here, like download a file.
// For our sample, we just sleep for 5 seconds.
long endTime = System.currentTimeMillis() + 5*1000;
while (System.currentTimeMillis() < endTime) {
synchronized (this) {
try {
wait(endTime - System.currentTimeMillis());
} catch (Exception e) {
}
}
}
// Stop the service using the startId, so that we don't stop
// the service in the middle of handling another job
stopSelf(msg.arg1);
}
}
@Override
public void onCreate() {
// Start up the thread running the service. Note that we create a
// separate thread because the service normally runs in the process's
// main thread, which we don't want to block. We also make it
// background priority so CPU-intensive work will not disrupt our UI.
HandlerThread thread = new HandlerThread("ServiceStartArguments",
Process.THREAD_PRIORITY_BACKGROUND);
thread.start();
// Get the HandlerThread's Looper and use it for our Handler
mServiceLooper = thread.getLooper();
mServiceHandler = new ServiceHandler(mServiceLooper);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();
// For each start request, send a message to start a job and deliver the
// start ID so we know which request we're stopping when we finish the job
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
mServiceHandler.sendMessage(msg);
// If we get killed, after returning from here, restart
return START_STICKY;
}
@Override
public IBinder onBind(Intent intent) {
// We don't provide binding, so return null
return null;
}
@Override
public void onDestroy() {
Toast.makeText(this, "service done", Toast.LENGTH_SHORT).show();
}
}

正如您所见,与使用 IntentService 相比,这需要执行更多工作。

但是,因为是由您自己处理对 onStartCommand() 的每个调用,因此可以同时执行多个请求。此示例并未这样做,但如果您希望如此,则可为每个请求创建一个新线程,然后立即运行这些线程(而不是等待上一个请求完成)。

请注意,onStartCommand() 方法必须返回整型数。整型数是一个值,用于描述系统应该如何在服务终止的情况下继续运行服务(如上所述,IntentService 的默认实现将为您处理这种情况,不过您可以对其进行修改)。从 onStartCommand() 返回的值必须是以下常量之一:

  • START_NOT_STICKY

    如果系统在 onStartCommand() 返回后终止服务,则除非有挂起 Intent 要传递,否则系统不会重建服务。这是最安全的选项,可以避免在不必要时以及应用能够轻松重启所有未完成的作业时运行服务。

  • START_STICKY

    如果系统在 onStartCommand() 返回后终止服务,则会重建服务并调用 onStartCommand(),但绝对不会重新传递最后一个 Intent。相反,除非有挂起 Intent 要启动服务(在这种情况下,将传递这些 Intent ),否则系统会通过空 Intent 调用 onStartCommand()。这适用于不执行命令、但无限期运行并等待作业的媒体播放器(或类似服务)。

  • START_REDELIVER_INTENT

    如果系统在 onStartCommand() 返回后终止服务,则会重建服务,并通过传递给服务的最后一个 Intent 调用 onStartCommand()。任何挂起 Intent 均依次传递。这适用于主动执行应该立即恢复的作业(例如下载文件)的服务。

有关这些返回值的更多详细信息,请查阅每个常量链接的参考文档。

启动服务

您可以通过将 Intent(指定要启动的服务)传递给 startService(),从 Activity 或其他应用组件启动服务。Android 系统调用服务的 onStartCommand() 方法,并向其传递 Intent。(切勿直接调用 onStartCommand()。)

例如,Activity 可以结合使用显式 Intent 与 startService(),启动上文中的示例服务 (HelloSevice):

1
2
Intent intent = new Intent(this, HelloService.class);
startService(intent);

startService() 方法将立即返回,且 Android 系统调用服务的 onStartCommand() 方法。如果服务尚未运行,则系统会先调用 onCreate(),然后再调用 onStartCommand()。

如果服务亦未提供绑定,则使用 startService() 传递的 Intent 是应用组件与服务之间唯一的通信模式。但是,如果您希望服务返回结果,则启动服务的客户端可以为广播创建一个 PendingIntent (使用 getBroadcast()),并通过启动服务的 Intent 传递给服务。然后,服务就可以使用广播传递结果。

多个服务启动请求会导致多次对服务的 onStartCommand() 进行相应的调用。但是,要停止服务,只需一个服务停止请求(使用 stopSelf() 或 stopService())即可。

停止服务

启动服务必须管理自己的生命周期。也就是说,除非系统必须回收内存资源,否则系统不会停止或销毁服务,而且服务在 onStartCommand() 返回后会继续运行。因此,服务必须通过调用 stopSelf() 自行停止运行,或者由另一个组件通过调用 stopService() 来停止它。

一旦请求使用 stopSelf() 或 stopService() 停止服务,系统就会尽快销毁服务。

但是,如果服务同时处理多个 onStartCommand() 请求,则您不应在处理完一个启动请求之后停止服务,因为您可能已经收到了新的启动请求(在第一个请求结束时停止服务会终止第二个请求)。为了避免这一问题,您可以使用 stopSelf(int) 确保服务停止请求始终基于最近的启动请求。也就说,在调用 stopSelf(int) 时,传递与停止请求的 ID 对应的启动请求的 ID(传递给 onStartCommand() 的 startId) 。然后,如果在您能够调用 stopSelf(int) 之前服务收到了新的启动请求, ID 就不匹配,服务也就不会停止。

  • 注意:

    为了避免浪费系统资源和消耗电池电量,应用必须在工作完成之后停止其服务。 如有必要,其他组件可以通过调用 stopService() 来停止服务。即使为服务启用了绑定,一旦服务收到对 onStartCommand() 的调用,您始终仍须亲自停止服务。

如需了解有关服务生命周期的详细信息,请参阅下面有关管理服务生命周期的部分。

创建绑定服务

绑定服务允许应用组件通过调用 bindService() 与其绑定,以便创建长期连接(通常不允许组件通过调用 startService() 来启动它)。

如需与 Activity 和其他应用组件中的服务进行交互,或者需要通过进程间通信 (IPC) 向其他应用公开某些应用功能,则应创建绑定服务。

要创建绑定服务,必须实现 onBind() 回调方法以返回 IBinder,用于定义与服务通信的接口。然后,其他应用组件可以调用 bindService() 来检索该接口,并开始对服务调用方法。服务只用于与其绑定的应用组件,因此如果没有组件绑定到服务,则系统会销毁服务(您不必按通过 onStartCommand() 启动的服务那样来停止绑定服务)。

要创建绑定服务,首先必须定义指定客户端如何与服务通信的接口。 服务与客户端之间的这个接口必须是 IBinder 的实现,并且服务必须从 onBind() 回调方法返回它。一旦客户端收到 IBinder,即可开始通过该接口与服务进行交互。

多个客户端可以同时绑定到服务。客户端完成与服务的交互后,会调用 unbindService() 取消绑定。一旦没有客户端绑定到该服务,系统就会销毁它。

有多种方法实现绑定服务,其实现比启动服务更为复杂,因此绑定服务将在有关绑定服务的单独文档中专门讨论。

向用户发送通知

一旦运行起来,服务即可使用 Toast 通知或状态栏通知来通知用户所发生的事件。

Toast 通知是指出现在当前窗口的表面、片刻随即消失不见的消息,而状态栏通知则在状态栏提供内含消息的图标,用户可以选择该图标来采取操作(例如启动 Activity)。

通常,当某些后台工作已经完成(例如文件下载完成)且用户现在可以对其进行操作时,状态栏通知是最佳方法。 当用户从展开视图中选定通知时,通知即可启动 Activity(例如查看已下载的文件)。

如需了解详细信息,请参阅 Toast 通知或状态栏通知开发者指南。

在前台运行服务

前台服务被认为是用户主动意识到的一种服务,因此在内存不足时,系统也不会考虑将其终止。 前台服务必须为状态栏提供通知,状态栏位于“正在进行”标题下方,这意味着除非服务停止或从前台删除,否则不能清除通知。

例如,应该将从服务播放音乐的音乐播放器设置为在前台运行,这是因为用户明确意识到其操作。 状态栏中的通知可能表示正在播放的歌曲,并允许用户启动 Activity 来与音乐播放器进行交互。

要请求让服务运行于前台,请调用 startForeground()。此方法取两个参数:唯一标识通知的整型数和状态栏的 Notification。例如:

1
2
3
4
5
Notification notification = new Notification(R.drawable.icon,getText(R.string.ticker_text), System.currentTimeMillis());
Intent notificationIntent = new Intent(this, ExampleActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
notification.setLatestEventInfo(this, getText(R.string.notification_title), getText(R.string.notification_message), pendingIntent);
startForeground(ONGOING_NOTIFICATION_ID, notification);
  • 注意:

    提供给 startForeground() 的整型 ID 不得为 0。

要从前台删除服务,请调用 stopForeground()。此方法取一个布尔值,指示是否也删除状态栏通知。 此方法绝对不会停止服务。 但是,如果您在服务正在前台运行时将其停止,则通知也会被删除。

如需了解有关通知的详细信息,请参阅创建状态栏通知。

管理服务生命周期

服务的生命周期比 Activity 的生命周期要简单得多。但是,密切关注如何创建和销毁服务反而更加重要,因为服务可以在用户没有意识到的情况下运行于后台。

服务生命周期(从创建到销毁)可以遵循两条不同的路径:

  • 启动服务

    该服务在其他组件调用 startService() 时创建,然后无限期运行,且必须通过调用 stopSelf() 来自行停止运行。此外,其他组件也可以通过调用 stopService() 来停止服务。服务停止后,系统会将其销毁。

  • 绑定服务

    该服务在另一个组件(客户端)调用 bindService() 时创建。然后,客户端通过 IBinder 接口与服务进行通信。客户端可以通过调用 unbindService() 关闭连接。多个客户端可以绑定到相同服务,而且当所有绑定全部取消后,系统即会销毁该服务。 (服务不必自行停止运行。)

这两条路径并非完全独立。也就是说,您可以绑定到已经使用 startService() 启动的服务。例如,可以通过使用 Intent(标识要播放的音乐)调用 startService() 来启动后台音乐服务。随后,可能在用户需要稍加控制播放器或获取有关当前播放歌曲的信息时,Activity 可以通过调用 bindService() 绑定到服务。在这种情况下,除非所有客户端均取消绑定,否则 stopService() 或 stopSelf() 不会真正停止服务。

实现生命周期回调

与 Activity 类似,服务也拥有生命周期回调方法,您可以实现这些方法来监控服务状态的变化并适时执行工作。 以下框架服务展示了每种生命周期方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class ExampleService extends Service {
int mStartMode; // indicates how to behave if the service is killed
IBinder mBinder; // interface for clients that bind
boolean mAllowRebind; // indicates whether onRebind should be used
@Override
public void onCreate() {
// The service is being created
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// The service is starting, due to a call to startService()
return mStartMode;
}
@Override
public IBinder onBind(Intent intent) {
// A client is binding to the service with bindService()
return mBinder;
}
@Override
public boolean onUnbind(Intent intent) {
// All clients have unbound with unbindService()
return mAllowRebind;
}
@Override
public void onRebind(Intent intent) {
// A client is binding to the service with bindService(),
// after onUnbind() has already been called
}
@Override
public void onDestroy() {
// The service is no longer used and is being destroyed
}
}
  • 注:

    与 Activity 生命周期回调方法不同,您不需要调用这些回调方法的超类实现。

    这里写图片描述

图 2. 服务生命周期左图显示了使用 startService() 所创建的服务的生命周期,右图显示了使用 bindService() 所创建的服务的生命周期。

通过实现这些方法,您可以监控服务生命周期的两个嵌套循环:

  • 服务的整个生命周期从调用 onCreate() 开始起,到 onDestroy() 返回时结束。与 Activity 类似,服务也在 onCreate() 中完成初始设置,并在 onDestroy() 中释放所有剩余资源。例如,音乐播放服务可以在 onCreate() 中创建用于播放音乐的线程,然后在 onDestroy() 中停止该线程。无论服务是通过 startService() 还是 bindService() 创建,都会为所有服务调用 onCreate() 和 onDestroy() 方法。

  • 服务的有效生命周期从调用 onStartCommand() 或 onBind() 方法开始。每种方法均有 Intent 对象,该对象分别传递到 startService() 或 bindService()。
    对于启动服务,有效生命周期与整个生命周期同时结束(即便是在 onStartCommand() 返回之后,服务仍然处于活动状态)。对于绑定服务,有效生命周期在 onUnbind() 返回时结束。

  • 注:

    尽管启动服务是通过调用 stopSelf() 或 stopService() 来停止,但是该服务并无相应的回调(没有 onStop() 回调)。因此,除非服务绑定到客户端,否则在服务停止时,系统会将其销毁—onDestroy() 是接收到的唯一回调。

图 2 说明了服务的典型回调方法。尽管该图分开介绍通过 startService() 创建的服务和通过 bindService() 创建的服务,但是请记住,不管启动方式如何,任何服务均有可能允许客户端与其绑定。因此,最初使用 onStartCommand()(通过客户端调用 startService())启动的服务仍可接收对 onBind() 的调用(当客户端调用 bindService() 时)。

如需了解有关创建提供绑定的服务的详细信息,请参阅绑定服务文档,该文档的管理绑定服务的生命周期部分提供了有关 onRebind() 回调方法的更多信息。

科大讯飞离线语音合成(语记)

发表于 2015-08-30 | 分类于 语音 |

转载请说明出处!
作者:kqw攻城狮
出处:个人站 | CSDN


离线语音合成(语记)

讯飞的语音合成有三种方式

  1. 在线语音合成(免费)
  2. 离线使用语记语音合成(免费,需要本地装一个语记App并且下载离线资源)
  3. 使用讯飞离线语音包(付费)

这里使用语记实现离线语音合成

效果图

效果图

源码

下载地址(Android Studio工程):http://download.csdn.net/detail/q4878802/9063593

说明

使用语记实现离线语音合成和在线语音合成的步骤非常相似,下载SDK的方式是一样的,一样是选择在线语音合成,只不过是使用离线引擎就可以借用语音合成语音了。

在线语音合成地址:http://blog.csdn.net/q4878802/article/details/48092495

下面说说和在线语音合成不用的地方

1. 下载并安装语记,下载离线资源

离线语音听写的文章里有介绍,地址: http://blog.csdn.net/q4878802/article/details/47834601

2. 网络权限就可以删掉了,本地合成不需要联网,但是获取网络状态的权限一定要有。

1
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

3. 将识别引擎改为本地引擎

1
2
// 引擎类型 本地
mTts.setParameter(SpeechConstant.ENGINE_TYPE, SpeechConstant.TYPE_LOCAL);

4. 在Application初始化的类里把引擎模式设置的参数注释掉

1
2
// param.append(",");
// param.append(SpeechConstant.ENGINE_MODE + "=" + SpeechConstant.MODE_MSC);

SpeechConstant.MODE_MSC 参数意思是使用离线包资源,如果离线包资源找不到会走网络识别,如果设置这种模式是不会使用语记(语音+)的如果使用离线包,就需要这条参数。

科大讯飞离线语音合成

发表于 2015-08-30 | 分类于 语音 |

转载请说明出处!
作者:kqw攻城狮
出处:个人站 | CSDN


离线语音合成(离线资源包)

讯飞的语音合成有三种方式

  1. 在线语音合成(免费)
  2. 离线使用语记语音合成(免费,需要本地装一个语记App并且下载离线资源)
  3. 使用讯飞离线语音包(付费)

这里使用离线资源包实现离线语音合成,因为正式版是要付费的,所以这里使用试用的离线包(35天试用期、3个装机量)。

效果图

效果图

源码

下载地址(Android Studio工程):http://download.csdn.net/detail/q4878802/9063779

开通服务,下载SDK

之前已经介绍过,地址:http://blog.csdn.net/q4878802/article/details/47762169#t8

将SDK里提供的jar包、so库、离线资源都拷贝到我们的工程(Android Studio工程)

创建的工程默认可能没有jniLibs和assets目录,我们要自己在main下创建这两个目录

导入SDK

初始化

在清单文件中application标签下添加name属性

1
android:name=".InitApplication"

初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package com.example.kongqw.kqwspeechcompounddemo;
import android.app.Application;
import android.widget.Toast;
import com.iflytek.cloud.SpeechConstant;
import com.iflytek.cloud.SpeechUtility;
/**
* Created by kongqw on 2015/8/29.
*/
public class InitApplication extends Application {
@Override
public void onCreate() {
Toast.makeText(this, "InitApplication", Toast.LENGTH_LONG).show();
// 应用程序入口处调用,避免手机内存过小,杀死后台进程后通过历史intent进入Activity造成SpeechUtility对象为null
// 如在Application中调用初始化,需要在Mainifest中注册该Applicaiton
// 注意:此接口在非主进程调用会返回null对象,如需在非主进程使用语音功能,请增加参数:SpeechConstant.FORCE_LOGIN+"=true"
// 参数间使用“,”分隔。
// 设置你申请的应用appid
StringBuffer param = new StringBuffer();
param.append("appid=55d33f09");
param.append(",");
param.append(SpeechConstant.ENGINE_MODE + "=" + SpeechConstant.MODE_MSC);
// param.append(",");
// param.append(SpeechConstant.FORCE_LOGIN + "=true");
SpeechUtility.createUtility(InitApplication.this, param.toString());
super.onCreate();
}
}

语音合成工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
package com.example.kongqw.kqwspeechcompounddemo.engine;
import com.iflytek.cloud.ErrorCode;
import com.iflytek.cloud.InitListener;
import com.iflytek.cloud.SpeechConstant;
import com.iflytek.cloud.SpeechError;
import com.iflytek.cloud.SpeechSynthesizer;
import com.iflytek.cloud.SynthesizerListener;
import com.iflytek.cloud.util.ResourceUtil;
import com.iflytek.cloud.util.ResourceUtil.RESOURCE_TYPE;
import android.content.Context;
import android.os.Bundle;
import android.util.Log;
import android.widget.Toast;
/**
* 语音合成器
*
* @author kongqw
*/
public class KqwSpeechSynthesizer {
// Log标签
private static final String TAG = "KqwSpeechSynthesizer";
private Context mContext;
// 语音合成对象
private SpeechSynthesizer mTts;
public KqwSpeechSynthesizer(Context context) {
mContext = context;
// 初始化合成对象
mTts = SpeechSynthesizer.createSynthesizer(context, new InitListener() {
@Override
public void onInit(int code) {
Log.d(TAG, "InitListener init() code = " + code);
if (code != ErrorCode.SUCCESS) {
Toast.makeText(mContext, "初始化失败,错误码:" + code, Toast.LENGTH_SHORT).show();
}
}
});
}
/**
* 开始语音合成
*
* @param text
*/
public void start(String text) {
// 设置参数
setParam();
int code = mTts.startSpeaking(text, mTtsListener);
if (code != ErrorCode.SUCCESS) {
Toast.makeText(mContext, "语音合成失败,错误码: " + code, Toast.LENGTH_SHORT).show();
}
}
/**
* 合成回调监听。
*/
private SynthesizerListener mTtsListener = new SynthesizerListener() {
@Override
public void onSpeakBegin() {
Log.i(TAG, "开始合成");
}
@Override
public void onSpeakPaused() {
Log.i(TAG, "暂停合成");
}
@Override
public void onSpeakResumed() {
Log.i(TAG, "继续合成");
}
@Override
public void onBufferProgress(int percent, int beginPos, int endPos, String info) {
Log.i(TAG, "传冲进度 :" + percent);
}
@Override
public void onSpeakProgress(int percent, int beginPos, int endPos) {
Log.i(TAG, "合成进度 : " + percent);
}
@Override
public void onCompleted(SpeechError error) {
if (error == null) {
Log.i(TAG, "合成完成");
} else if (error != null) {
Log.i(TAG, "error : " + error.toString());
}
}
@Override
public void onEvent(int arg0, int arg1, int arg2, Bundle arg3) {
// TODO Auto-generated method stub
}
};
/**
* 参数设置
*
* @return
*/
private void setParam() {
// 清空参数
mTts.setParameter(SpeechConstant.PARAMS, null);
// 设置使用本地引擎
mTts.setParameter(SpeechConstant.ENGINE_TYPE, SpeechConstant.TYPE_LOCAL);
// 设置发音人资源路径
mTts.setParameter(ResourceUtil.TTS_RES_PATH, getResourcePath());
// 设置发音人
mTts.setParameter(SpeechConstant.VOICE_NAME, "xiaoyan");
// 设置语速
mTts.setParameter(SpeechConstant.SPEED, "50");
// 设置音调
mTts.setParameter(SpeechConstant.PITCH, "50");
// 设置音量
mTts.setParameter(SpeechConstant.VOLUME, "50");
// 设置播放器音频流类型
mTts.setParameter(SpeechConstant.STREAM_TYPE, "3");
}
// 获取发音人资源路径
private String getResourcePath() {
StringBuffer tempBuffer = new StringBuffer();
// 合成通用资源
tempBuffer.append(ResourceUtil.generateResourcePath(mContext, RESOURCE_TYPE.assets, "tts/common.jet"));
tempBuffer.append(";");
// 发音人资源
tempBuffer.append(ResourceUtil.generateResourcePath(mContext, RESOURCE_TYPE.assets, "tts/xiaoyan.jet"));
return tempBuffer.toString();
}
}

测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package com.example.kongqw.kqwspeechcompounddemo;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.EditText;
import android.widget.Toast;
import com.example.kongqw.kqwspeechcompounddemo.engine.KqwSpeechSynthesizer;
public class MainActivity extends Activity {
private EditText mEtText;
private KqwSpeechSynthesizer mKqwSpeechSynthesizer;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mEtText = (EditText) findViewById(R.id.et_text);
// 初始化语音合成对象
mKqwSpeechSynthesizer = new KqwSpeechSynthesizer(this);
}
/**
* 开始合成
*
* @param view
*/
public void start(View view) {
Toast.makeText(this, "开始合成 : " + mEtText.getText().toString().trim(), Toast.LENGTH_SHORT).show();
mKqwSpeechSynthesizer.start(mEtText.getText().toString().trim());
}
}

XML页面布局

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".MainActivity">
<EditText
android:id="@+id/et_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="语音合成的内容"
android:textSize="20dp" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/et_text"
android:gravity="center"
android:onClick="start"
android:text="语音合成"
android:textSize="20dp" />
</RelativeLayout>

说明

因为我用的是离线资源包,试用期是有35天并且只有3个装机量,所以如果直接使用我的demo可能会有问题,如果要自己再创建一个工程,千万不要忘记替换APPID、库、离线资源。

科大讯飞语义识别

发表于 2015-08-30 | 分类于 语音 |

转载请说明出处!
作者:kqw攻城狮
出处:个人站 | CSDN


效果图

效果图

源码

下载地址(Android Studio工程):http://download.csdn.net/detail/q4878802/9064463

语义测试接口

地址:http://osp.voicecloud.cn/index.php/default/quicktest/index

测试接口

开通服务,下载SDK

之前已经介绍过,地址:http://blog.csdn.net/q4878802/article/details/47762169#t8

这里说一下,进入到SDK的下载界面,你发现找不到语义的服务,而在我们开通服务的时候都是默认就帮我们把语义的服务开启了,可能是因为语义是只能用网络的,没有本地的资源,所以只要选择一个在线的功能,使用的jar包应该都是一样的,为什么没有直接下载语义的SDK我也不是很清楚,但是都可以用。

说明

之前的工程都是在Eclipse下演示的,随着Android Studio的普及,我这里也开始使用Android Studio写Demo,虽然导入jar包和so库的过程可能不太一样,但是整体的流程是一样的。

将jar包和so库导入Android Studio工程

将jar包copy到libs目录下

在main目录下创建jniLibs目录,将so文件copy过来

将jar包copy到libs目录下

初始化

在清单文件中application标签下添加name属性

1
android:name=".InitApplication"

初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package com.example.kongqw.kqwunderstanddemo;
import android.app.Application;
import android.widget.Toast;
import com.iflytek.cloud.SpeechConstant;
import com.iflytek.cloud.SpeechUtility;
/**
* Created by kongqw on 2015/8/29.
*/
public class InitApplication extends Application {
@Override
public void onCreate() {
Toast.makeText(this, "InitApplication", Toast.LENGTH_LONG).show();
// 应用程序入口处调用,避免手机内存过小,杀死后台进程后通过历史intent进入Activity造成SpeechUtility对象为null
// 如在Application中调用初始化,需要在Mainifest中注册该Applicaiton
// 注意:此接口在非主进程调用会返回null对象,如需在非主进程使用语音功能,请增加参数:SpeechConstant.FORCE_LOGIN+"=true"
// 参数间使用“,”分隔。
// 设置你申请的应用appid
StringBuffer param = new StringBuffer();
param.append("appid=55d33f09");
param.append(",");
param.append(SpeechConstant.ENGINE_MODE + "=" + SpeechConstant.MODE_MSC);
// param.append(",");
// param.append(SpeechConstant.FORCE_LOGIN + "=true");
SpeechUtility.createUtility(InitApplication.this, param.toString());
super.onCreate();
}
}

语义理解工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
package com.example.kongqw.kqwunderstanddemo.engine;
import android.content.Context;
import android.text.TextUtils;
import android.util.Log;
import android.widget.Toast;
import com.iflytek.cloud.ErrorCode;
import com.iflytek.cloud.InitListener;
import com.iflytek.cloud.SpeechError;
import com.iflytek.cloud.TextUnderstander;
import com.iflytek.cloud.TextUnderstanderListener;
import com.iflytek.cloud.UnderstanderResult;
/**
* 科大讯飞语义理解
*
* @author kongqw
*/
public abstract class KqwUnderstander {
public abstract void result(String json);
// 上下文
private Context mContext;
// Log标记
private static final String TAG = "KqwUnderstander";
// 语义理解对象(文本到语义)。
private TextUnderstander mTextUnderstander;
/**
* 构造方法
*
* @param context
*/
public KqwUnderstander(Context context) {
// 上下文
mContext = context;
// 初始化语义理解对象
mTextUnderstander = TextUnderstander.createTextUnderstander(context, textUnderstanderListener);
}
// TODO 返回科大讯飞返回Json的实体类的对象
public String textUnderstander(String text) {
if (mTextUnderstander.isUnderstanding()) {
mTextUnderstander.cancel();
} else {
int ret = mTextUnderstander.understandText(text, textListener);
if (ret != 0) {
Toast.makeText(mContext, "语义理解失败,错误码:" + ret, Toast.LENGTH_SHORT).show();
}
}
return null;
}
/**
* 初始化监听器(文本到语义)。
*/
private InitListener textUnderstanderListener = new InitListener() {
@Override
public void onInit(int code) {
Log.d(TAG, "textUnderstanderListener init() code = " + code);
if (code != ErrorCode.SUCCESS) {
Toast.makeText(mContext, "初始化失败,错误码:" + code, Toast.LENGTH_SHORT).show();
}
}
};
private TextUnderstanderListener textListener = new TextUnderstanderListener() {
@Override
public void onResult(final UnderstanderResult result) {
if (null != result) {
// 显示
Log.d(TAG, "understander result:" + result.getResultString());
// Toast.makeText(mContext, "understander result:" + result.getResultString(), Toast.LENGTH_SHORT).show();
String text = result.getResultString();
if (!TextUtils.isEmpty(text)) {
result(text);
}
} else {
Log.d(TAG, "understander result:null");
Toast.makeText(mContext, "识别结果不正确。", Toast.LENGTH_SHORT).show();
}
}
@Override
public void onError(SpeechError error) {
Toast.makeText(mContext, "onError Code:" + error.getErrorCode(), Toast.LENGTH_SHORT).show();
}
};
}

测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package com.example.kongqw.kqwunderstanddemo;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import com.example.kongqw.kqwunderstanddemo.engine.KqwUnderstander;
public class MainActivity extends Activity {
private EditText mEtText;
private KqwUnderstander mKqwUnderstander;
private TextView mTvShow;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mEtText = (EditText) findViewById(R.id.et_text);
mTvShow = (TextView) findViewById(R.id.tv_show);
// 初始化语音合成对象
mKqwUnderstander = new KqwUnderstander(this) {
@Override
public void result(String json) {
mTvShow.setText(json);
// Toast.makeText(MainActivity.this, "json = " + json, Toast.LENGTH_SHORT).show();
}
};
}
/**
* 语义理解
*
* @param view
*/
public void start(View view) {
Toast.makeText(this, "语义理解 : " + mEtText.getText().toString().trim(), Toast.LENGTH_SHORT).show();
mKqwUnderstander.textUnderstander(mEtText.getText().toString().trim());
}
}

XML页面布局

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".MainActivity">
<EditText
android:id="@+id/et_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="上帝到西二旗怎么走"
android:textSize="20dp" />
<Button
android:id="@+id/button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/et_text"
android:gravity="center"
android:onClick="start"
android:text="语义理解"
android:textSize="20dp" />
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@+id/button">
<TextView
android:id="@+id/tv_show"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</ScrollView>
</RelativeLayout>

注意

在使用语义之前一定要开通对应的语义场景

科大讯飞在线语音合成

发表于 2015-08-29 | 分类于 语音 |

转载请说明出处!
作者:kqw攻城狮
出处:个人站 | CSDN


效果图

效果图
效果图

源码

下载地址(Android Studio工程):http://download.csdn.net/detail/q4878802/9062261

下载SDK

1. 选择服务

选择服务

2. 选择平台

选择平台

3. 选择应用

选择应用

4. 下载SDK

说明

之前的工程都是在Eclipse下演示的,随着Android Studio的普及,我这里也开始使用Android Studio写Demo,虽然细节导入jar包和so库的过程可能不太一样,但是整体的流程是一样的。

将jar包和so库导入Android Studio工程

将jar包copy到libs目录下
在main目录下创建jniLibs目录,将so文件copy过来

导入SDK

添加网络权限

1
2
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET"/>

初始化

在清单文件中application标签下添加name属性

1
android:name=".InitApplication"

初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package com.example.kongqw.kqwspeechcompounddemo;
import android.app.Application;
import android.widget.Toast;
import com.iflytek.cloud.SpeechConstant;
import com.iflytek.cloud.SpeechUtility;
/**
* Created by kongqw on 2015/8/29.
*/
public class InitApplication extends Application {
@Override
public void onCreate() {
Toast.makeText(this, "InitApplication", Toast.LENGTH_LONG).show();
// 应用程序入口处调用,避免手机内存过小,杀死后台进程后通过历史intent进入Activity造成SpeechUtility对象为null
// 如在Application中调用初始化,需要在Mainifest中注册该Applicaiton
// 注意:此接口在非主进程调用会返回null对象,如需在非主进程使用语音功能,请增加参数:SpeechConstant.FORCE_LOGIN+"=true"
// 参数间使用“,”分隔。
// 设置你申请的应用appid
StringBuffer param = new StringBuffer();
param.append("appid=55d33f09");
param.append(",");
param.append(SpeechConstant.ENGINE_MODE + "=" + SpeechConstant.MODE_MSC);
// param.append(",");
// param.append(SpeechConstant.FORCE_LOGIN + "=true");
SpeechUtility.createUtility(InitApplication.this, param.toString());
super.onCreate();
}
}

语音合成工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
package com.example.kongqw.kqwspeechcompounddemo.engine;
import android.content.Context;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.widget.Toast;
import com.iflytek.cloud.ErrorCode;
import com.iflytek.cloud.InitListener;
import com.iflytek.cloud.SpeechConstant;
import com.iflytek.cloud.SpeechError;
import com.iflytek.cloud.SpeechSynthesizer;
import com.iflytek.cloud.SynthesizerListener;
/**
* 语音合成的类 发音人明细http://bbs.xfyun.cn/forum.php?mod=viewthread&tid=367
*
* @author kongqw
*/
public class KqwSpeechCompound {
// Log标签
private static final String TAG = "KqwSpeechCompound";
// 上下文
private Context mContext;
// 语音合成对象
private static SpeechSynthesizer mTts;
/**
* 发音人
*/
public final static String[] COLOUD_VOICERS_ENTRIES = {"小燕", "小宇", "凯瑟琳", "亨利", "玛丽", "小研", "小琪", "小峰", "小梅", "小莉", "小蓉", "小芸", "小坤", "小强 ", "小莹",
"小新", "楠楠", "老孙",};
public final static String[] COLOUD_VOICERS_VALUE = {"xiaoyan", "xiaoyu", "catherine", "henry", "vimary", "vixy", "xiaoqi", "vixf", "xiaomei",
"xiaolin", "xiaorong", "xiaoqian", "xiaokun", "xiaoqiang", "vixying", "xiaoxin", "nannan", "vils",};
/**
* 构造方法
*
* @param context
*/
public KqwSpeechCompound(Context context) {
// 上下文
mContext = context;
// 初始化合成对象
mTts = SpeechSynthesizer.createSynthesizer(mContext, new InitListener() {
@Override
public void onInit(int code) {
if (code != ErrorCode.SUCCESS) {
Log.i(TAG, "初始化失败,错误码:" + code);
}
}
});
}
/**
* 开始合成
*
* @param text
*/
public void speaking(String text) {
// 非空判断
if (TextUtils.isEmpty(text)) {
return;
}
int code = mTts.startSpeaking(text, mTtsListener);
if (code != ErrorCode.SUCCESS) {
if (code == ErrorCode.ERROR_COMPONENT_NOT_INSTALLED) {
Toast.makeText(mContext, "没有安装语音+ code = " + code, Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(mContext, "语音合成失败,错误码: " + code, Toast.LENGTH_SHORT).show();
}
}
}
/*
* 停止语音播报
*/
public static void stopSpeaking() {
// 对象非空并且正在说话
if (null != mTts && mTts.isSpeaking()) {
// 停止说话
mTts.stopSpeaking();
}
}
/**
* 判断当前有没有说话
*
* @return
*/
public static boolean isSpeaking() {
if (null != mTts) {
return mTts.isSpeaking();
} else {
return false;
}
}
/**
* 合成回调监听。
*/
private SynthesizerListener mTtsListener = new SynthesizerListener() {
@Override
public void onSpeakBegin() {
Log.i(TAG, "开始播放");
}
@Override
public void onSpeakPaused() {
Log.i(TAG, "暂停播放");
}
@Override
public void onSpeakResumed() {
Log.i(TAG, "继续播放");
}
@Override
public void onBufferProgress(int percent, int beginPos, int endPos, String info) {
// TODO 缓冲的进度
Log.i(TAG, "缓冲 : " + percent);
}
@Override
public void onSpeakProgress(int percent, int beginPos, int endPos) {
// TODO 说话的进度
Log.i(TAG, "合成 : " + percent);
}
@Override
public void onCompleted(SpeechError error) {
if (error == null) {
Log.i(TAG, "播放完成");
} else if (error != null) {
Log.i(TAG, error.getPlainDescription(true));
}
}
@Override
public void onEvent(int eventType, int arg1, int arg2, Bundle obj) {
}
};
/**
* 参数设置
*
* @return
*/
private void setParam() {
// 清空参数
mTts.setParameter(SpeechConstant.PARAMS, null);
// 引擎类型 网络
mTts.setParameter(SpeechConstant.ENGINE_TYPE, SpeechConstant.TYPE_CLOUD);
// 设置发音人
mTts.setParameter(SpeechConstant.VOICE_NAME, COLOUD_VOICERS_VALUE[0]);
// 设置语速
mTts.setParameter(SpeechConstant.SPEED, "50");
// 设置音调
mTts.setParameter(SpeechConstant.PITCH, "50");
// 设置音量
mTts.setParameter(SpeechConstant.VOLUME, "100");
// 设置播放器音频流类型
mTts.setParameter(SpeechConstant.STREAM_TYPE, "3");
// mTts.setParameter(SpeechConstant.TTS_AUDIO_PATH, Environment.getExternalStorageDirectory() + "/KRobot/wavaudio.pcm");
// 背景音乐 1有 0 无
// mTts.setParameter("bgs", "1");
}
}

测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package com.example.kongqw.kqwspeechcompounddemo;
import android.app.Activity;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.EditText;
import android.widget.Toast;
import com.example.kongqw.kqwspeechcompounddemo.engine.KqwSpeechCompound;
public class MainActivity extends Activity {
private EditText mEtText;
private KqwSpeechCompound mKqwSpeechCompound;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mEtText = (EditText) findViewById(R.id.et_text);
// 初始化语音合成对象
mKqwSpeechCompound = new KqwSpeechCompound(this);
}
/**
* 开始合成
*
* @param view
*/
public void start(View view) {
Toast.makeText(this, "开始合成 : " + mEtText.getText().toString().trim(), Toast.LENGTH_SHORT).show();
mKqwSpeechCompound.speaking(mEtText.getText().toString().trim());
}
}

XML页面布局

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".MainActivity">
<EditText
android:id="@+id/et_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="语音合成的内容"
android:textSize="20dp" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/et_text"
android:gravity="center"
android:onClick="start"
android:text="语音合成"
android:textSize="20dp" />
</RelativeLayout>
1…456
kongqw

kongqw

Android Developer

54 日志
4 分类
25 标签
GitHub CSDN
Links
  • 干货集中营
  • 讯飞开放平台
  • 环信
  • 百度统计
  • DISQUS评论
  • BTC Wallet
  • OKCoin中国站
  • OKCoin国际站
  • BTC.com
© 2015 - 2018 kongqw
由 Hexo 强力驱动
主题 - NexT.Pisces