1. 程式人生 > >android 融雲 + 科大訊飛 實現仿微信語音訊息轉換為文字(附DEMO原始碼)

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


效果