语音唤醒实现

栏目: IOS · Android · 发布时间: 5年前

内容简介:现在无论是智能音箱还是手机或者平板,都可以在不管cpu是否休眠的时候喊一声“小爱同学”或者“小布小布”就可以唤醒了。那么哪些实现是比较好的呢,或者存在哪些未知的坑呢?今年从过完年后就一直在做语音唤醒这块,一款好的,有价值的项目,会有很多的问题需要去解决。这里先抛出几个问题。肯定是放在系统的framework层,应用存在各种crash以及被强杀等,及时是系统应用,其存活完全不能和系统服务相比。

现在无论是智能音箱还是手机或者平板,都可以在不管cpu是否休眠的时候喊一声“小爱同学”或者“小布小布”就可以唤醒了。那么哪些实现是比较好的呢,或者存在哪些未知的坑呢?今年从过完年后就一直在做语音唤醒这块,一款好的,有价值的项目,会有很多的问题需要去解决。

1.前言

这里先抛出几个问题。

1.语音唤醒的实现放在android哪一层处理会比较好?

肯定是放在系统的framework层,应用存在各种crash以及被强杀等,及时是系统应用,其存活完全不能和系统服务相比。

2.需不需要回声消除去处理音频?

这个也是需要的,AEC可以比较大的降低误唤醒率影响。这里我吐槽下我的小米mix2s手机,这个误唤醒率实在太高了。看视频的时候经常误唤醒,及其烦人,所以我把整个语音唤醒都关了。我们是把AEC放在hal层下,通过AudioRecord提供到上层实现

3.为了降低误唤醒,还有哪些比较好的办法?

可以对一级唤醒后的唤醒词做二级校验,降低误唤醒率。

4.ASR放在哪一层处理?

这个放在应用层做识别处理即可

5.framework层如何拉起应用?

唤醒后,拉起应用应该快速,如果等了2,3秒,屏幕才亮起来,这个体验就会很差,所以不建议使用广播的方式,因为framework层通过广播通知应用起来的话,在连接wifi或者开机时,系统会收到大量的广播事件,android会一个一个处理广播事件,这里会导致应用起来的比较慢。所以可以选择直接拉起应用的activity或者service来实现拉起应用。

Slog.d(TAG, "notifyWakeup");
        Intent intent = new Intent();
        ...
        ...
        intent.putExtra("topPackage", getTopPackage());
        intent.putExtra("topActivity", getTopActivity());
        mContext.startService(intent);
复制代码

2.唤醒方案实现

2.1.低功耗语音唤醒方案实现

所谓的低功耗语音唤醒方案无非就是加语音唤醒芯片,确保在cpu休眠的时候它还在继续工作,当用户说唤醒词时,会将唤醒事件通知到framework层,从而唤醒cpu,触发整个唤醒流程。

但是低功耗唤醒方案存在的问题是,如果不加以对唤醒词的数据校验的话误唤醒率相对较高。这样就会让用户很难接受。所以需要加二级唤醒校验,譬如如果使用dspg的话,会从soundtrigger获取到唤醒词数据,再将唤醒词数据送给二级语音模型训练库,从而降低唤醒率。

private SoundTriggerDetector.Callback mSoundTriggerDetectorCallback = new SoundTriggerDetector.Callback() {

        @Override
        public void onAvailabilityChanged(int status) {
            Slog.d(TAG, "SoundTriggerDetectorCallback onAvailabilityChanged status=" + status);
        }

        @Override
        public void onDetected(SoundTriggerDetector.EventPayload eventPayload) {
            Slog.d(TAG, "SoundTriggerDetectorCallback onDetected");
            if (mAuthDUI.isAvailable() && startWakeupApp()) {
                byte[] triggeraudio = eventPayload.getTriggerAudio();
                if (triggeraudio != null) {
                    mSingleEngine.feed(triggeraudio, triggeraudio.length);
                    Slog.d(TAG, "feed trigger to single engine end");
                }
            }
        }

        @Override
        public void onError() {
            Slog.d(TAG, "SoundTriggerDetectorCallback onError");
        }


        @Override
        public void onRecognitionPaused() {
            Slog.d(TAG, "SoundTriggerDetectorCallback onRecognitionPaused");
        }

        @Override
        public void onRecognitionResumed() {
            Slog.d(TAG, "SoundTriggerDetectorCallback onRecognitionResumed");
        }
    };
复制代码

2.2.二级唤醒实现

二级唤醒的实现其实就是在cpu没有休眠,或者没有灭屏的时候通过audiorecord获取音频数据送给语音唤醒模型库来实现。如果是在cpu没有休眠时,走二级唤醒,可以让内核加个事件上报机制,告诉framework层系统没有休眠,继续二级唤醒,这里需要说下为什么要走二级唤醒?因为二级唤醒会有AEC,会降低误唤醒率。

二级唤醒的实现其实也就是在framework层开一个录音机,在系统服务一起来的时候就打开录音机,然后只要没有切换到cpu休眠状态时,都走二级唤醒,录音机不断地将唤醒数据喂给语音唤醒训练库。当检测到有唤醒事件时,可以选择拉起service的方式拉起应用。

这里在预言阶段就测试了,在cpu没有休眠的时候存在一个audiorecord的话,对功耗影响并不会很多,完全可以接受。这里需要修改audio这块,让android支持多录音,不然上层的app无法正常录音。

private void startSingleRecord() {
        startSingleRecord(MediaRecorder.AudioSource.VOICE_RECOGNITION, AudioFormat.CHANNEL_IN_MONO);
    }
复制代码

需要注意的是多验证audiorecord,防止存在创建了多个audiorecord但是没有销毁导致功耗降不下去的问题。

我一般都是直接dumpsys audio_flinger来查看当前的录音数量和pid。

adb shell dumpsys media.audio_flinger
复制代码

2.3.状态切换中导致的唤不醒

在一级切换二级,或者二级切换一级过程中,会存在你喊“小布小布”时,第一个小布在二级中,第二个小布在一级中,导致一级和二级都没有唤醒。所以如果在亮灭屏时判断的时候,可以不要去监听亮灭屏广播去实现,直接在Notifier中去监听亮灭屏,去避免切换的这200多毫秒。

private void handleEarlyInteractiveChange() {
        synchronized (mLock) {
            if (mInteractive) {
                // Waking up...
                mHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        EventLog.writeEvent(EventLogTags.POWER_SCREEN_STATE, 1, 0, 0, 0);
                        mPolicy.startedWakingUp();
                        if (mAIManagerInternal != null) {
                            mAIManagerInternal.startedWakingUp();
                        } else {
                            Slog.d(TAG, "AIManagerInternal is null");
                        }

                    }
                });

                // Send interactive broadcast.
                mPendingInteractiveState = INTERACTIVE_STATE_AWAKE;
                mPendingWakeUpBroadcast = true;
                updatePendingBroadcastLocked();
            } else {
                // Going to sleep...
                // Tell the policy that we started going to sleep.
                final int why = translateOffReason(mInteractiveChangeReason);
                mHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        mPolicy.startedGoingToSleep(why);
                        if (mAIManagerInternal != null) {
                            mAIManagerInternal.startedGoingToSleep(why);
                        } else {
                            Slog.d(TAG, "AIManagerInternal is null");
                        }
                    }
                });
            }
        }
    }
复制代码

在自定义系统服务中去实现这俩个函数。

final class LocalService extends AIManagerInternal {
        @Override
        public void startedGoingToSleep(int why) {
            synchronized (AIManagerService.this) {
                AIManagerService.this.startedGoingToSleep(why);
            }
        }

        @Override
        public void startedWakingUp() {
            synchronized (AIManagerService.this) {
                AIManagerService.this.startedWakingUp();
            }
        }
    }
复制代码

3.如何避免丢音

当你通过语音去唤醒时,如果你快速的说,譬如“小布小布,打开设置”。不管在亮屏还是灭屏的时候可能都会存在丢音,最后送给到上层去做识别的音频数据只有“设置”,那么如何避免丢音呢。做缓存,无论是一级唤醒还是二级唤醒,framework层都应该做音频数据的缓存,缓存足够的数据,不管应用什么时候来取,都应该缓存足量的数据,确保避免丢音问题的存在。

private ArrayBlockingQueue<byte[]> mBlockingQueue;
...
...
mBlockingQueue = new ArrayBlockingQueue<>(1024);
复制代码

对于dspg,则应该做oneshot来缓存唤醒点前面1-2秒的音频数据,确保在从dspg唤醒后,开一个audiorecord拿到的音频数据是缓存了唤醒点前1-2秒的。

4.其他点

整个语音唤醒中还有很多点和坑,以上是目前想到的几点。

下面是项目中用到的一些地方,方便借鉴。

1.判断系统有无应用在录音,譬如在灭屏但是有应用在录音时,这个时候可以不用切成一级唤醒,为了充分利用aec,可以走二级唤醒,如何去判断应用录音以及录音结束后收到通知呢?

判断系统中有无应用录音

public boolean isAppRecording() {
        List<AudioRecordingConfiguration> configs = mAudioManager.getActiveRecordingConfigurations();
        Slog.d(TAG, "recording configs.size=" + configs.size());
        if (configs.size() > 1) {
            Slog.d(TAG, "has other app recording");
            return true;
        }
        Slog.d(TAG, "no app is recording");
        return false;
    }
    
    
复制代码

注册监听,在录音状态改变是收到回调通知

AudioManager.AudioRecordingCallback mAudioRecordingCallback = new AudioManager.AudioRecordingCallback() {
        @Override
        public void onRecordingConfigChanged(List<AudioRecordingConfiguration> configs) {
            super.onRecordingConfigChanged(configs);
            Slog.d(TAG, "onRecordingConfigChanged configs.size:" + configs.size());
            if (configs.size() == 1 && !isScreenOn()) {
                Slog.d(TAG, "onRecordingConfigChanged switch to dspg wakeup");
                //stop software wakeup,start dspg wakeup
                mWorkHandler.removeMessages(MSG_SWITCH_TO_SINGLEOFF_MIC);
                mWorkHandler.sendEmptyMessage(MSG_SWITCH_TO_SINGLEOFF_MIC);

            }
        }
    };
复制代码

判断系统中有无应用在放音

public boolean isAudioPlayback() {
        List<AudioPlaybackConfiguration> configs = mAudioManager.getActivePlaybackConfigurations();
        for (AudioPlaybackConfiguration config : configs) {
            if (config.isActive()) {
                Slog.d(TAG, "has other app audioplaing,pid is:" + config.getClientPid());
                return true;
            }
        }
        Slog.d(TAG, "no app audio is playing");
        return false;
    }
复制代码

应用放音结束时收到系统放音变化的注册回调

AudioManager.AudioPlaybackCallback mAudioPlaybackCallback = new AudioManager.AudioPlaybackCallback() {
        @Override
        public void onPlaybackConfigChanged(List<AudioPlaybackConfiguration> configs) {
            super.onPlaybackConfigChanged(configs);
            {
                boolean active = false;
                Slog.d(TAG, "AudioPlaybackConfiguration.size" + configs.size());
                for (AudioPlaybackConfiguration config : configs) {
                    if (config.isActive()) {
                        active = true;
                    }
                }
                Slog.d(TAG, "active:" + active);
                if (!active && !isScreenOn()) {
                    Slog.d(TAG, "onPlaybackConfigChanged switch to dspg wakeup");
                    //stop aispeech wakeup,start dspg wakeup
                    mWorkHandler.removeMessages(MSG_SWITCH_TO_SINGLEOFF_MIC);
                    mWorkHandler.sendEmptyMessage(MSG_SWITCH_TO_SINGLEOFF_MIC);

                }
                if (active && !isScreenOn()) {
                    Slog.d(TAG, "onPlaybackConfigChanged switch to aispeech wakeup");
                    //stop dspg wakeup and start aispeech wakeup
                    mWorkHandler.removeMessages(MSG_SWITCH_TO_SINGLEON_MIC);
                    mWorkHandler.sendEmptyMessage(MSG_SWITCH_TO_SINGLEON_MIC);
                }
            }
        }

    };
复制代码

以上所述就是小编给大家介绍的《语音唤醒实现》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

MySQL性能调优与架构设计

MySQL性能调优与架构设计

简朝阳 / 2009-6 / 59.80元

《MySQL性能调优与架构设计》以 MySQL 数据库的基础及维护为切入点,重点介绍了 MySQL 数据库应用系统的性能调优,以及高可用可扩展的架构设计。 全书共分3篇,基础篇介绍了MySQL软件的基础知识、架构组成、存储引擎、安全管理及基本的备份恢复知识。性能优化篇从影响 MySQL 数据库应用系统性能的因素开始,针对性地对各个影响因素进行调优分析。如 MySQL Schema 设计的技巧......一起来看看 《MySQL性能调优与架构设计》 这本书的介绍吧!

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具

html转js在线工具
html转js在线工具

html转js在线工具