您的位置:首页 > 移动开发 > 微信开发

android 融云 + 科大讯飞 实现仿微信语音消息转换为文字

2017-04-20 10:30 2336 查看
融云SDK 使用很方便,简单配置就可以搭建即时通讯功能,配合科大讯飞的语音识别, 即可实现微信中语音消息转换为文字的功能



融云sdk的基本使用就不细说了, 网上很多资料

使用融云sdk自带的聊天会话界面,想要在此会话界面上增加语音消息长按时弹出 “转换为文字” 的菜单, 只需实现聊天会话界面的事件监听即可,监听类为:

ConversationBehaviorListener


融云默认的消息点击事件由MessageListAdapter 类设置,即在消息列表的适配器中定义点击,长按等事件处理器。
其中的消息长按事件处理代码为:
MessageListAdapter类的 bindView()方法

view.setOnLongClickListener(new OnLongClickListener() {
public boolean onLongClick(View v) {
if(RongContext.getInstance().getConversationBehaviorListener() != null && RongContext.getInstance().getConversationBehaviorListener().onMessageLongClick(MessageListAdapter.this.mContext, v, data)) {
return false;
} else {
MessageProvider provider = RongContext.getInstance().getMessageTemplate(data.getContent().getClass());
provider.onItemLongClick(v, position, data.getContent(), data);
return false;
}
}
});


可以发现,当ConversationBehaviorListener的onMessageLongClick方法返回true时 ,不再执行融云默认的消息长按处理,
返回fasle即由融云默认的MessageProvider来负责处理。

因此我们只需要实现自定义的ConversationBehaviorListener ,重写其中的 onMessageLongClick方法,并返回true,即可实现自定义的消息长按事件
,我们这里的需求是 实现语音消息长按,弹出 “转换为文字” 的菜单。


初始化融云之后,设置自定义的会话界面事件监听器:

/*init rongcloud*/

RongIM.init(this);
RongIM.setConversationBehaviorListener(new RongIM.ConversationBehaviorListener() {
@Override
public boolean onUserPortraitClick(Context context, Conversation.ConversationType conversationType, UserInfo userInfo) {
return false;
}

@Override
public boolean onUserPortraitLongClick(Context context, Conversation.ConversationType conversationType, UserInfo userInfo) {
return false;
}

@Override
public boolean onMessageClick(Context context, View view, Message message) {
return false;
}

@Override
public boolean onMessageLongClick(Context context, View view, Message message) {
//会话界面消息长按回调方法 ,如果是语音消息则使用自定义的 MyVoiceMessageItemProvider ,否则使用融云默认处理器
if(message.getContent() instanceof VoiceMessage){
IContainerItemProvider.MessageProvider provider = new MyVoiceMessageItemProvider(context);
provider.onItemLongClick(view, 0, message.getContent(), message);
return true;
}else{
return false;   //返回false ,使用融云默认处理
}

}
});


重写了 onMessageLongClick 方法。 由于只需要增加对语音消息的处理,所以先对消息类型判断,如果是语音消息VoiceMessage则
自定义处理器MyVoiceMessageItemProvider ,该类的实现后面会讲。其他消息(文字,图片)由融云默认处理。

设置了语音消息长按的事件监听器后,接下来实现 “转化为文字” 菜单的弹出 。
融云默认的语音消息长按弹出菜单由 RongIM类初始化时注册的 VoiceMessageItemProvider 类实现,如下:

RongIM 类中的init()方法:
registerMessageTemplate(new TextMessageItemProvider());
registerMessageTemplate(new ImageMessageItemProvider());
registerMessageTemplate(new LocationMessageItemProvider());
registerMessageTemplate(new VoiceMessageItemProvider(context));
registerMessageTemplate(new DiscussionNotificationMessageItemProvider());
registerMessageTemplate(new InfoNotificationMsgItemProvider());
registerMessageTemplate(new RichContentMessageItemProvider());
registerMessageTemplate(new PublicServiceMultiRichContentMessageProvider());
registerMessageTemplate(new PublicServiceRichContentMessageProvider());
registerMessageTemplate(new HandshakeMessageItemProvider());
registerMessageTemplate(new UnknownMessageItemProvider());

VoiceMessageItemProvider 类中处理消息长按的代码:
public void onItemLongClick(View view, int position, VoiceMessage content, final Message message) {
String name = null;
if(!message.getConversationType().getName().equals(ConversationType.APP_PUBLIC_SERVICE.getName()) && !message.getConversationType().getName().equals(ConversationType.PUBLIC_SERVICE.getName())) {
UserInfo items1 = (UserInfo)RongContext.getInstance().getUserInfoCache().get(message.getSenderUserId());
if(items1 != null) {
name = items1.getName();
}
} else {
ConversationKey items = ConversationKey.obtain(message.getTargetId(), message.getConversationType());
PublicServiceInfo info = (PublicServiceInfo)RongContext.getInstance().getPublicServiceInfoCache().get(items.getKey());
if(info != null) {
name = info.getName();
}
}

String[] items2 = new String[]{view.getContext().getResources().getString(string.rc_dialog_item_message_delete)};
ArraysDialogFragment.newInstance(name, items2).setArraysDialogItemListener(new OnArraysDialogItemListener() {
public void OnArraysDialogItemClick(DialogInterface dialog, int which) {
if(which == 0) {
RongIM.getInstance().getRongIMClient().deleteMessages(new int[]{message.getMessageId()}, (ResultCallback)null);
}

}
}).show(((FragmentActivity)view.getContext()).getSupportFragmentManager());
}

只实现了 长按时弹出“删除”按钮 ,并删除此消息的功能。
我们只需要继承此类,并重写此onItemLongClick方法,在其中增加一个 “转化为文字”的弹出按钮,在配合科大讯飞的语音识别功能,即可实现微信那样的语音消息转文字功能。

自定义的MyVoiceMessageItemProvider类,继承自VoiceMessageItemProvider
/**
* 会话界面事件处理类
*/
public class MyVoiceMessageItemProvider extends VoiceMessageItemProvider {
private  Context context;
//转换后显示文字
private  TextView textView;

public MyVoiceMessageItemProvider(Context context) {
super(context);
this.context = context;
}

//语音消息长按处理回调方法
@Override
public void onItemLongClick(View view, int position, final VoiceMessage content, final Message message) {
String name = null;
if(!message.getConversationType().getName().equals(Conversation.ConversationType.APP_PUBLIC_SERVICE.getName()) && !message.getConversationType().getName().equals(Conversation.ConversationType.PUBLIC_SERVICE.getName())) {
UserInfo items1 = (UserInfo)RongContext.getInstance().getUserInfoCache().get(message.getSenderUserId());
if(items1 != null) {
name = items1.getName();
}
} else {
ConversationKey items = ConversationKey.obtain(message.getTargetId(), message.getConversationType());
PublicServiceInfo info = (PublicServiceInfo)RongContext.getInstance().getPublicServiceInfoCache().get(items.getKey());
if(info != null) {
name = info.getName();
}
}

String[] items2 = new String[]{view.getContext().getResources().getString(io.rong.imkit.R.string.rc_dialog_item_message_delete),
view.getContext().getResources().getString(io.rong.imkit.R.string.rc_dialog_item_message_convert)};
ArraysDialogFragment.newInstance(name, items2).setArraysDialogItemListener(new ArraysDialogFragment.OnArraysDialogItemListener() {
public void OnArraysDialogItemClick(DialogInterface dialog, int which) {
if(which == 0) {
RongIM.getInstance().getRongIMClient().deleteMessages(new int[]{message.getMessageId()}, (RongIMClient.ResultCallback)null);
}
else if(which == 1){

//初始化 语音转化为文字 界面
LayoutInflater factory = LayoutInflater.from(context);
RelativeLayout view = (RelativeLayout)factory.inflate(R.layout.convert_dialog, null);
final AlertDialog dlg = new AlertDialog.Builder(context).create();
textView = new TextView(context);
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
params.addRule(RelativeLayout.CENTER_IN_PARENT);
textView.setLayoutParams(params);
textView.setTextColor(Color.BLACK);
textView.setTextSize(20f);
textView.setText("正在转换...");
view.addView(textView);
if ( !dlg.isShowing()) {
dlg.show();
}

dlg.setContentView(view);

Activity activity = (Activity) context;
String voicePath = FileUtils.uri2File(activity,content.getUri());

//调用科大讯飞处理类解析语音文件
new IflytekHandle(voicePath,context){
@Override
public  void returnWords(String words){
textView.setText(words);
}
};
}

}
}).show(((FragmentActivity)view.getContext()).getSupportFragmentManager());
}

}

该类在语音消息长按时 ,弹出“删除” ,“转换为文字”两个按钮 ,点击 “转换为文字” ,弹出一个Dialog ,并将语音消息中的音频文件URI地址转为绝对路径后,交由科大讯飞识别,识别成功后显示在Dialog 中。负责处理语音识别的类为 IflytekHandle ,
下面会贴出代码。

由于科大讯飞只能识别 pcm和wav格式的音频流文件,而融云的语音消息文件格式为 AMR,因此识别前需将本地的AMR录音文件解码为pcm。解码类 AudioDecode 来自于网上开源
/**
* 科大讯飞语音识别工具
*/

public  abstract  class IflytekHandle {

// 用HashMap存储听写结果
private HashMap<String,String> mIatResults = new LinkedHashMap<>();
private static SpeechRecognizer mIat;
// 引擎类型
private String mEngineType = SpeechConstant.TYPE_CLOUD;
//解码转换
private AudioDecode audioDecode;

public IflytekHandle(String filePath , Context context){
voice2words(filePath,context);
}

public void voice2words (String filePath , Context context){
mIatResults.clear();
if(mIat == null){
//1、创建SpeechRecognizer对象,第二个参数:本地识别时传InitListener
mIat = SpeechRecognizer.createRecognizer(context,null);
setParam();
mIat.setParameter(SpeechConstant.AUDIO_SOURCE, "-1");
mIat.setParameter(SpeechConstant.SAMPLE_RATE, "8000");//设置正确的采样率
}
int ret = 0; // 函数调用返回值
ret = mIat.startListening(mRecognizerListener);

if (ret != ErrorCode.SUCCESS) {

} else {
//iatFun();//讯飞demo里面的方法
audioDecodeFun(filePath);
}
}

//听写监听器
private RecognizerListener mRecognizerListener = new RecognizerListener() {

//volume音量值0~30,data音频数据
@Override
public void onVolumeChanged(int volume, byte[] bytes) {

}
//开始录音
// 此回调表示:sdk内部录音机已经准备好了,用户可以开始语音输入
@Override
public void onBeginOfSpeech() {

}
//结束录音
@Override
public void onEndOfSpeech() {

}

/**
* 听写结果回调接口,返回Json格式结果
* 一般情况下会通过onResults接口多次返回结果,完整的识别内容是多次结果的累加
* isLast等于true时会话结束。
*/
@Override
public void onResult(RecognizerResult recognizerResult, boolean b) {
printResult(recognizerResult);
}

//会话发生错误回调接口
// Tips:
// 错误码:10118(您没有说话),可能是录音机权限被禁,需要提示用户打开应用的录音权限。
@Override
public void onError(SpeechError speechError) {
returnWords(speechError.getErrorDescription());
}
//扩展用接口
@Override
public void onEvent(int eventType, int arg1, int arg2, Bundle bundle) {

}
};

private void printResult(RecognizerResult recognizerResult) {
String text = JsonParser.parseIatResult(recognizerResult.getResultString());
String sn = null;
//读取Json结果中的sn字段
try {
JSONObject resultJson = new JSONObject(recognizerResult.getResultString());
sn = resultJson.optString("sn");
}catch (Exception e){
e.printStackTrace();
}
mIatResults.put(sn,text);
StringBuilder sb = new StringBuilder();
for (String key:mIatResults.keySet()){
sb.append(mIatResults.get(key));
}

returnWords(sb.toString());
}
//回调方法 ,返回识别后的文字
public abstract void returnWords(String words);

/**
* 工具类
* @param audioPath
*/
private void audioDecodeFun(String audioPath){
audioDecode = AudioDecode.newInstance();
audioDecode.setFilePath(audioPath);
audioDecode.prepare();
audioDecode.setOnCompleteListener(new AudioDecode.OnCompleteListener() {
@Override
public void completed(final ArrayList<byte[]> pcmData) {
if(pcmData!=null){
//写入音频文件数据,数据格式必须是采样率为8KHz或16KHz(本地识别只支持16K采样率,云端都支持),位长16bit,单声道的wav或者pcm
//必须要先保存到本地,才能被讯飞识别
//为防止数据较长,多次写入,把一次写入的音频,限制到 64K 以下,然后循环的调用wirteAudio,直到把音频写完为止
for (byte[] data : pcmData){
mIat.writeAudio(data, 0, data.length);
}
Log.d("-----------stop",System.currentTimeMillis()+"");
mIat.stopListening();
}else{
mIat.cancel();
}
audioDecode.release();
}
});
audioDecode.startAsync();
}

/**
* 参数设置
*/
private void setParam(){
//参数设置
/**
* 应用领域 服务器为不同的应用领域,定制了不同的听写匹配引擎,使用对应的领域能获取更 高的匹配率
* 应用领域用于听写和语音语义服务。当前支持的应用领域有:
* 短信和日常用语:iat (默认)
* 视频:video
* 地图:poi
* 音乐:music
*/
mIat.setParameter(SpeechConstant.DOMAIN,"iat");
/**
* 在听写和语音语义理解时,可通过设置此参数,选择要使用的语言区域
* 当前支持:
* 简体中文:zh_cn(默认)
* 美式英文:en_us
*/
mIat.setParameter(SpeechConstant.LANGUAGE,"zh_cn");
/**
* 每一种语言区域,一般还有不同的方言,通过此参数,在听写和语音语义理解时, 设置不同的方言参数。
* 当前仅在LANGUAGE为简体中文时,支持方言选择,其他语言区域时, 请把此参数值设为null。
* 普通话:mandarin(默认)
* 粤 语:cantonese
* 四川话:lmz
* 河南话:henanese
*/
mIat.setParameter(SpeechConstant.ACCENT,"mandarin");
// 设置听写引擎
mIat.setParameter(SpeechConstant.ENGINE_TYPE, mEngineType);
//设置语音前端点:静音超时时间,即用户多长时间不说话则当做超时处理
//默认值:短信转写5000,其他4000
mIat.setParameter(SpeechConstant.VAD_BOS,"4000");
// 设置语音后端点:后端点静音检测时间,即用户停止说话多长时间内即认为不再输入, 自动停止录音
mIat.setParameter(SpeechConstant.VAD_EOS,"1000");
// 设置标点符号,设置为"0"返回结果无标点,设置为"1"返回结果有标点
mIat.setParameter(SpeechConstant.ASR_PTT,"1");
// 设置音频保存路径,保存音频格式支持pcm、wav
mIat.setParameter(SpeechConstant.AUDIO_FORMAT,"wav");
//mIat.setParameter(SpeechConstant.ASR_AUDIO_PATH, Environment.getExternalStorageDirectory()+"/msc/iat.wav");
//文本,编码
mIat.setParameter(SpeechConstant.TEXT_ENCODING, "utf-8");
}

}


效果


内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: