android 融云 + 科大讯飞 实现仿微信语音消息转换为文字(附DEMO源码)

 

 

融云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"); } }

 

 


效果

 

 

 

 

 源码下载(科大的SKD文件即jniLibs目录下的文件需替换为自己的)

 

 

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值