声网是一家提供语音、视频即时通讯服务的公司,他的服务大多基于WebRTC开源项目并进行一些优化和修改。而讯飞语音识别应该不用多说了,老罗在发布会上介绍得已经够详细了。

那么下面进入今天的主题,就是让声网和讯飞识别同时使用,之前可能有朋友没遇到过这样的需求,那先说一下让两者同时使用会出现啥问题,为什么要做修改呢?其实原因很简单,即时通讯过程中毫无疑问肯定会用到麦克风和扬声器的,而语音识别呢,麦克风当然也是必须的了,好,那问题来了,同时有两个地方需要调用麦克风,Android系统到底要分配给谁呢?经测试,这问题对于Android5.0和5.1一点问题都没有,他们好像对麦克风这个硬件资源进行了抽象和封装,所有调用者其实拿的都是实际音频流的一份拷贝。但是其他系统一旦同时使用这两者,就肯定会报出AudioRecord -38的错误,而且每次都是讯飞识别报出,因为声网每次启动通讯时都会把麦克风资源给抢了。。。好,既然这样,我们就得另辟蹊径了。

经过思考,由于讯飞提供自定义音频源的方式,因此我们决定从改变讯飞音频源的方式入手,但是由于声网的加入通讯和退出通讯是随时都可能发生的,因此,如果每次切换都要改变讯飞的配置,那么两者的耦合性太大了,如果以后音频源不止原生AudioRecord和声网,那么又得修改讯飞了,这显然是不符合软件工程开发的思想的。所以我们最后决定用发布/订阅者模式进行设计,首先弄一个manager管理所有订阅者和当前发布者,这里发布和订阅者之间的关系显然是1对多的,因此订阅者是一个列表,而发布者就应该是一个成员对象。然后定义发布者和订阅者两者的接口,其中发布者的接口就应该包括开启录音和关闭录音,而订阅者的接口就更简单,通知有音频源到来就行。废话不再多说,先上代码。

public class XLAudioRecordManager {
    private static XLAudioRecordManager instance = null;
    private List subscribors = new ArrayList<>();
    private final static String TAG = XLAudioRecordManager.class.getSimpleName();
    private XLAudioRecord internalAudioPublisher; // 内部的音频提供者
    private XLAudioRecordPublisherCallback curPublisher; // 只需要一个发布者

    public void setCurPublisher(XLAudioRecordPublisherCallback curPublisher) {
        this.curPublisher = curPublisher;
    }

    public void initCurPublisher() {
        curPublisher = internalAudioPublisher;
    }

    private XLAudioRecordManager() {
        internalAudioPublisher = new XLAudioRecord();
        initCurPublisher();
    }

    public static XLAudioRecordManager getInstance() {
        if (instance == null) {
            instance = new XLAudioRecordManager();
        }
        return instance;
    }

    public void writeAudio(byte[] audioBuffer, int offset, int length) {
        for (XLAudioRecordSubscriberCallback callback : subscribors) {
            callback.onAudio(audioBuffer, offset, length);
        }
    }

    public void subscribe(XLAudioRecordSubscriberCallback callback) {
        this.subscribors.add(callback);
    }

    public void unSubscribe(XLAudioRecordSubscriberCallback callback) {
        this.subscribors.remove(callback);
    }

    // 订阅者接口
    public interface XLAudioRecordSubscriberCallback {
        void onAudio(byte[] audioData, int offset, int length);
    }

    // 发布者接口
    public interface XLAudioRecordPublisherCallback {
        void onStartRecording();
        void onStopRecording();
    }

    public void startRecording() {
        // 通知发布者开始采集音频流
        curPublisher.onStartRecording();
    }

    public void stopRecording() {
        // 通知发布者停止采集音频流
        curPublisher.onStopRecording();
    }

}
可以从上面代码中看到,该管理还维护了一个内部的音频源发布者,其实就是原生的AudioRecord,这样外部也不需要知道没有声网介入时音频流从何而来了。OK,下面可以通过看一下这个XLAudioRecord了解发布者是怎么实现的。

public class XLAudioRecord implements XLAudioRecordManager.XLAudioRecordPublisherCallback {
    private AudioRecord mAudioRecord = null;
    private boolean isRecording = false; // 判断AudioRecord是否需要开启
    public static final int SAMPLE_RATE = 16000;
    private int mMinbuffer = 1000;
    public static final int CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_MONO;
    public static final int AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
    private final static String TAG = XLAudioRecord.class.getSimpleName();
//    private AcousticEchoCanceler acousticEchoCanceler;

    public XLAudioRecord() {
        mMinbuffer = AudioRecord.getMinBufferSize(SAMPLE_RATE, CHANNEL_CONFIG, AUDIO_FORMAT);
        if (mMinbuffer != AudioRecord.ERROR_BAD_VALUE) {
//            initAudioRecord();
//            acousticEchoCanceler = AcousticEchoCanceler.create(mAudioRecord.getAudioSessionId());
//            acousticEchoCanceler.setEnabled(true);

        } else {
            Log.e(TAG, "AudioRecord getMinBuffer error");
        }
    }

    private void initAudioRecord() {
        int trytimes = 0;
        while (true) {
            try {
                mAudioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, SAMPLE_RATE,
                        CHANNEL_CONFIG, AUDIO_FORMAT, mMinbuffer);
                if (mAudioRecord.getRecordingState() == AudioRecord.STATE_INITIALIZED) {
                    break;
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            if (trytimes >= 5) {
                Log.e(TAG, "AudioRecord initialize error");
                break;
            }
            trytimes++;
        }
    }

    @Override
    public void onStartRecording() {
        isRecording = true;
        new Thread(new Runnable() {
            @Override
            public void run() {
                initAudioRecord();
                mAudioRecord.startRecording();
                while (isRecording) {
                    byte[] audioData = new byte[mMinbuffer];
                    int bufferSize = mAudioRecord.read(audioData, 0, mMinbuffer);
                    try {
                        Thread.sleep(40);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    XLAudioRecordManager.getInstance().writeAudio(audioData, 0, bufferSize);
                }
                if (mAudioRecord != null && mAudioRecord.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING) {
                    mAudioRecord.stop();
                    mAudioRecord.release();
                    mAudioRecord = null;
                }
            }
        }).start();
    }

    @Override
    public void onStopRecording() {
        isRecording = false;
    }
}
注意一下,onStopRecording中不能直接stop AudioRecord,而是将录音循环停止,使录音循环作为一个原子操作。

接下来,看一下声网的这个发布者是如何接入的,我们需要设置rtcengine的AudioFrame参数。

即时通讯-单聊功能表-聊天功能-聊天app-聊天信息加密沟通-聊天文本-聊天技术-聊天构架源码-聊天 记录同步云储存-聊天消息测回-哇谷即时通讯IM源码

mRtcEngine.setRecordingAudioFrameParameters(SAMPLE_RATE, 1, 0, 1024);
            mRtcEngine.registerAudioFrameObserver(audioFrameObserver);
其中AudioFrameObserver定义如下:
    private IAudioFrameObserver audioFrameObserver = new IAudioFrameObserver() {
        @Override
        public boolean onRecordFrame(byte[] bytes, int i, int i1, int i2, int i3) {

            if (isListening) {
                XLAudioRecordManager.getInstance().writeAudio(bytes, 0, bytes.length);
            }
            return true;
        }

        @Override
        public boolean onPlaybackFrame(byte[] bytes, int i, int i1, int i2, int i3) {
            return false;
        }
    };

还有,发布者接口的实现如下:

@Override
    public void onStartRecording() {
        isListening = true;
    }

    @Override
    public void onStopRecording() {
        isListening = false;
    }


最后,介绍一下订阅者讯飞的实现了:

public class IFlyRecognizer extends RecognizerAdapter implements XLAudioRecordManager.XLAudioRecordSubscriberCallback {

    private com.iflytek.cloud.RecognizerListener recognizerListener;
    private SpeechRecognizer speechRecognizer;
    private String userAudioPath = null;
    private static Context mContext;

    public IFlyRecognizer(Context context) {
        mContext = context;
        XLAudioRecordManager.getInstance().subscribe(this);
        speechRecognizer = SpeechRecognizer.createRecognizer(context, null);
        //2.设置听写参数
        speechRecognizer.setParameter(SpeechConstant.DOMAIN, "iat");
        speechRecognizer.setParameter(SpeechConstant.LANGUAGE, "zh_cn");
        speechRecognizer.setParameter(SpeechConstant.ACCENT, "mandarin");
        speechRecognizer.setParameter(SpeechConstant.PARAMS, null);
        speechRecognizer.setParameter(SpeechConstant.SAMPLE_RATE, "16000");
        //设置返回多个结果
        speechRecognizer.setParameter(SpeechConstant.ASR_NBEST, "5");
        // 设置语音前端点:静音超时时间,即用户多长时间不说话则当做超时处理
        speechRecognizer.setParameter(SpeechConstant.VAD_BOS, "8000");
        // 设置语音后端点:后端点静音检测时间,即用户停止说话多长时间内即认为不再输入, 自动停止录音
        speechRecognizer.setParameter(SpeechConstant.VAD_EOS, "1000");
        speechRecognizer.setParameter(SpeechConstant.ASR_PTT, "0");
        speechRecognizer.setParameter(SpeechConstant.AUDIO_SOURCE, "-1");
    }

    @Override
    public void setRecognizerListener(RecognizerListener listener) {
        this.recognizerListener = new IFlyRecognizerListener(listener);
    }

    @Override
    public void startRecognize() {
        //如果获取用户ID失败,则不保存录音文件
//        if (!Config.USER_ID.equals("")) {
//            speechRecognizer.setParameter(SpeechConstant.AUDIO_FORMAT, "wav");
//            speechRecognizer.setParameter(SpeechConstant.ASR_AUDIO_PATH, getAudioPathName());
//        }
        XLAudioRecordManager.getInstance().startRecording();
        speechRecognizer.startListening(recognizerListener);
        if (recognizerListener != null) {
            recognizerListener.onBeginOfSpeech();
        }
    }

    @Override
    public void stopRecognize() {
        speechRecognizer.stopListening();
        XLAudioRecordManager.getInstance().stopRecording();
    }

    @Override
    public void cancelRecognize() {
        speechRecognizer.cancel();
        XLAudioRecordManager.getInstance().stopRecording();
    }

    @Override
    public void onAudio(byte[] audioData, int offset, int length) {
        int res = speechRecognizer.writeAudio(audioData, offset, length);
//        if (res == ErrorCode.SUCCESS) {
//            Log.e("IFlyRecognizer", "写入成功");
//        } else {
//            Log.e("IFlyRecognizer", "写入失败");
//        }
    }
}
可以看到,初始化AUDIO_SOURCE时要设置为-1,这样才可以在onAudio中writeAudio到讯飞的Recognizer中。

好了,声网与讯飞的结合工作差不多讲完了,真心觉得当初学的设计模式对现在的代码编写有潜移默化的作用,希望对大家有所帮助吧。


--------------

热门搜索:

什么是私有云? 私有云、公有云还是混合云?企业该如何选? 企业聊天APP有什么作用,可以带来哪些便利? 企业选择混合云的优势 聊天APP应该具备哪些功能?怎么确保信息不被泄露? 企业即时通讯的使用价值有哪些 企业IM是什么,有什么使用优势? JM沟通优势有哪些,安全性怎么样? JM沟通APP功能强大且安全性高 IM云系统即时通讯公有云、私有云、企业云、海外云-哇谷IM团队 对于IM即时通讯的性能与并发性问题xmpp等各种通信协议 sso登录统一账号体系和集中认证授权 MongoDB数据库百万并发设计使用方法 WebRTC视频会议服务器性能10万并发 办公协同解决方案的意义 有什么优势? 美国FBI遭黑客攻击 超10万人收到垃圾邮件!这家零佣金券商也被攻击 用户数据泄露-企业即时通讯安全 工作台支持私有化部署-企业办公通信管理-软件开发管理系统-哇谷云 Application scenarios of wagu im cloud service A typical IM architecture might look like this 哇谷IM实现原理讲解-哇谷IM即时通讯云 JM沟通功能图片演示-哇谷IM即时通讯云 硬件配置选择-哇谷IM即时通讯云 IM私有化价格服务系统-哇谷IM即时通讯云 哇谷云服务项目表 哇谷IM定制私有化搭建资料准备工单-哇谷IM即时通讯云 APP中红包功能技术最常见的形式-哇谷im即时通讯云 私有云、公有云还是混合云?企业该如何选? 租用海外云服务器时应注意的事项 私有云比起公有云安全性大大提高 哇谷云服务应用场景十分丰富多元 聊天APP功能强大 用户群体越来越广泛 企业打造私有云平台需要注意的几点 企业聊天办公软件如何挑选,需要具备哪些功能? IM的作用可不只是单纯的聊天工具 企业使用即时通讯的好处有哪些? 企业通讯常见的困扰:有什么办法提高通讯质量? 聊天APP应该具备哪些功能?怎么确保信息不被泄露? 语音会议软件越来越多:到底哪个软件适合开会? 使用手机能否召开视频会议?哪些软件值得选择? JM沟通与传统通讯软件有何区别?是否只能用于社交? 语音会议效果难以保障 你有准备专业会议软件吗 专业企业通讯软件功能齐全 让办公变得更加轻松快捷 业应不应该开发聊天APP 三个不容拒绝的理由 企业通讯软件如何选择 隐私保护终于一切 视频会议没有专用软件 很多事情都无法处理 哪些是常用的微服务框架? 企业如何搭建私有云? 即时通讯软件是否具有聊天回执的功能? 企业即时通讯软件支持多设备登陆吗? 企业即时通讯适合中小企业的即时通讯软件? 盘点全球几大即时通讯软件 介绍两款国外大佬级别的即时通讯APP Short video features 即时通讯短视频功能的基本特点主要有哪些 Features of chat function 聊天功能在即时通讯软件中的产品特色体现在哪里 Increase the function of red envelope 为什么在即时通讯软件中会增加红包功能 instant messaging 即时通讯 办私密软件公 Office privacy software Cloud service 云服务 Im definition IM定义 How to meet office requirements Main features of instant messaging 即时通讯云软件应该如何达到集团办公的需求 企业即时通讯软件设计的特点主要有哪些 Instant messaging cloud red packet  福利发送新办法,IM即时通讯云红包功能的大势所趋 LAN communication 区域网通讯可以促进企业发展,使信息传输更快捷 Production guide of enterprise specific app 企业专属聊天APP的生产指南,这里应有尽有! Waguyun privacy protection 企业通讯无法得到私密保障?哇谷云上办公为您提供专业服务! Choose Wawa Guyun for internal communication 企业内部交流怎么办?现在有了哇谷云!  Advantages of LAN  局域网通讯企业真的需要吗?它有哪些优点? Enterprise instant messaging voice conference app 新发展、新机遇 全球疫情下的企业即时通讯语音会议 Provide efficient enterprise communication 专业呼叫中心 提供高效企业通信解决方案 New layout of global economic integration 协同办公解决方案,全球一体化经济新布局 There is more than one way for IM tools 还在使用微信?IM工具不止有一条途径 Power source for efficient operation 选择办公协同解决方案,现代企业高效运转的动力源 Efficient enterprise specific instant messaging 企业聊天APP,私密度高的高效企业专属即时通讯APP 新一代私有云与早期私有云的区别 The difference between private clouds What are the advantages of private cloud 新一代私有云有哪些优势? 单聊功能 群聊功能红包功能源码 实时音视频 短视频 音视频会议源码 低延迟直播 哇谷IM云服务 IM私有化搭建源码 私有化搭建准备文件 TF签名知识 企业即时通讯 IM 即时通讯 IM定制功能 语音聊天室 在线教育 金融服务 sso单点登录系统 政府加密沟通 企业通信办公 KTV点唱系统 局域网通讯 知识库 企业即时通讯 sso单点登录系统 KTV点唱系统版本Demo JM沟通2.0版本Demo 哇谷IM企业办公版本 哇谷工作台 短视频源码 音视频会议源码 直播聊天源码 ios超级签名服务 哇谷云 哇谷科技 即时通讯 IM下载http://www.juemigoutong.com/webproduct.html IM功能与价格http://www.juemigoutong.com/webcooperation.html 哇谷IM团队,哇谷即时通讯,哇谷官方站 即时通讯-哇谷即时通讯-IM-直播-短视频-音视频会议-大型企业即时通讯办-即时通讯源码-聊天app-http://www.wagukeji.com 即时通讯-哇谷即时通讯-IM-直播-短视频-音视频会议-大型企业即时通讯办-即时通讯源码-聊天app-sso-http://www.juemigoutong.com 即时通讯-哇谷即时通讯-IM-直播-短视频-音视频会议-大型企业即时通讯办-即时通讯源码-聊天app-http://www.wagu.cloud 即时通讯-哇谷即时通讯-IM-直播-短视频-音视频会议-大型企业即时通讯办-即时通讯源码-聊天app-http://bolg.wagu.cloud 即时通讯-哇谷即时通讯-IM-直播-短视频-音视频会议-大型企业即时通讯办-即时通讯源码-聊天app-http://so.wagu.cloud