以下是在 Android 集成科大讯飞离线语音测试中,处理 call.bnf
里 <contact>
为动态数据的一种常见方法示例,整体思路围绕构建合适的语法文件以及在代码中正确配置来实现:
一、语法文件 call.bnf
的构建(包含动态 <contact>
处理)
- 基本语法结构示例(简化示意):
#ABNF 1.0 UTF-8;
language zh-CN;
mode voice;
root $main;
$main = $call_command;
$call_command = "打电话给" <contact>;
<contact> = <contact_name_1> | <contact_name_2> | <contact_name_3>; // 这里先示例几个固定的,后续替换为动态数据
<contact_name_1> = "张三";
<contact_name_2> = "李四";
<contact_name_3> = "王五";
- 动态数据整合思路:
要让 <contact>
包含动态数据,通常可以在应用启动或者需要更新联系人数据时,从手机通讯录等数据源读取联系人姓名列表,然后按照 ABNF
语法规则的格式拼接成对应的语法规则字符串,去替换掉原来 <contact>
里固定的那些联系人示例。
例如,使用 Java 代码读取手机通讯录获取联系人姓名列表(这里假设使用 ContentResolver
来读取安卓系统的通讯录数据,并且已经处理好了相关权限申请等前置操作):
import android.content.ContentResolver;
import android.content.Context;
import android.database.Cursor;
import android.provider.ContactsContract;
import java.util.ArrayList;
import java.util.List;
public class ContactUtil {
public static List<String> getContactNames(Context context) {
List<String> contactNames = new ArrayList<>();
ContentResolver contentResolver = context.getContentResolver();
Cursor cursor = contentResolver.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
null, null, null, null);
if (cursor!= null && cursor.moveToFirst()) {
int nameIndex = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME);
do {
String contactName = cursor.getString(nameIndex);
contactNames.add(contactName);
} while (cursor.moveToNext());
cursor.close();
}
return contactNames;
}
}
然后根据获取到的联系人姓名列表构建动态的 <contact>
语法部分:
import java.util.List;
public class GrammarBuilder {
public static String buildContactGrammar(List<String> contactNames) {
StringBuilder contactGrammar = new StringBuilder("<contact> = ");
for (int i = 0; i < contactNames.size(); i++) {
contactGrammar.append("<contact_name_").append(i).append(">");
if (i < contactNames.size() - 1) {
contactGrammar.append(" | ");
}
contactGrammar.append("\n");
contactGrammar.append("<contact_name_").append(i).append("> = \"").append(contactNames.get(i)).append("\"");
if (i < contactNames.size() - 1) {
contactGrammar.append("\n");
}
}
return contactGrammar.toString();
}
}
二、在 Android 代码中集成并配置(结合科大讯飞离线语音识别)
- 初始化科大讯飞语音识别引擎(假设在
MainActivity
的onCreate
方法等合适位置):
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.util.ResourceUtil;
public class MainActivity extends AppCompatActivity {
private SpeechRecognizer mAsr;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mAsr = SpeechRecognizer.createRecognizer(this, null);
// 其他初始化相关操作等,比如设置离线资源路径等(参考之前科大讯飞离线语音集成示例)
String resourcePath = ResourceUtil.generateResourcePath(this, ResourceUtil.RESOURCE_TYPE.assets, "asr");
mAsr.setParameter(SpeechConstant.ASR_RES_PATH, resourcePath);
}
}
- 配置语法文件(将包含动态
<contact>
的语法应用到语音识别中):
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
import java.util.List;
public class MainActivity extends AppCompatActivity {
// 前面代码省略
private void setupGrammar() {
List<String> contactNames = ContactUtil.getContactNames(this);
String contactGrammar = GrammarBuilder.buildContactGrammar(contactNames);
// 读取原始的 call.bnf 模板文件内容(假设模板文件放在 assets 目录下)
String templateContent = readAssetFile("call.bnf");
// 替换掉 <contact> 相关部分语法内容
String finalGrammarContent = templateContent.replace("<contact> = <contact_name_1> | <contact_name_2> | <contact_name_3>;", contactGrammar);
// 将更新后的语法文件内容写入到一个临时文件(也可以根据需求保存到合适的内部存储位置等)
try {
File tempFile = File.createTempFile("call", ".bnf");
FileOutputStream fos = new FileOutputStream(tempFile);
OutputStreamWriter writer = new OutputStreamWriter(fos, StandardCharsets.UTF_8);
writer.write(finalGrammarContent);
writer.close();
fos.close();
// 设置语法文件路径参数给科大讯飞语音识别引擎
mAsr.setParameter(SpeechConstant.GRAMMAR_LIST, tempFile.getAbsolutePath());
} catch (IOException e) {
e.printStackTrace();
}
}
private String readAssetFile(String fileName) {
try {
InputStream is = getAssets().open(fileName);
ByteArrayOutputStream result = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int length;
while ((length = is.read(buffer))!= -1) {
result.write(buffer, 0, length);
}
return result.toString(StandardCharsets.UTF_8.name());
} catch (IOException e) {
e.printStackTrace();
return "";
}
}
@Override
protected void onResume() {
super.onResume();
setupGrammar();
}
// 点击按钮触发语音识别等操作(参考之前科大讯飞离线语音识别示例代码)
Button btnRecognize = findViewById(R.id.btn_recognize);
btnRecognize.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mAsr.setParameter(SpeechConstant.ENGINE_TYPE, SpeechConstant.TYPE_LOCAL);
mAsr.setParameter(SpeechConstant.LANGUAGE, "zh_cn");
mAsr.setParameter(SpeechConstant.ACCENT, "mandarin");
mAsr.startListening(new RecognizerListener() {
// 各种回调处理省略
});
}
});
}
在上述代码实现过程中:
- 首先通过读取手机通讯录获取联系人姓名列表,利用这些动态数据构建符合
ABNF
语法的<contact>
规则部分。 - 接着读取原始的
call.bnf
模板文件内容,替换其中<contact>
相关的固定语法内容,生成最终的语法文件内容。 - 然后将这个更新后的语法文件内容写入到一个临时文件,并将临时文件的绝对路径设置给科大讯飞语音识别引擎的语法文件路径参数,这样语音识别就能基于包含动态联系人信息的语法来识别语音指令了。
请注意:
- 实际应用中要妥善处理好权限问题,比如读取手机通讯录需要合适的权限声明(
READ_CONTACTS
权限等)以及在 Android 6.0 及以上系统中进行动态权限申请。 - 对于语法文件的更新、临时文件的管理等也要考虑到可能出现的异常情况,例如文件读写失败、内存占用等问题,确保整个语音识别功能的稳定可靠。