语音唤醒实现

栏目: 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);
                }
            }
        }

    };
复制代码

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

查看所有标签

猜你喜欢:

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

GWT in Action

GWT in Action

Robert Hanson、Adam Tacy / Manning Publications / 2007-06-05 / USD 49.99

This book will show Java developers how to use the Google Web Toolkit (GWT) to rapidly create rich web-based applications using their existing skills. It will cover the full development cycle, from ......一起来看看 《GWT in Action》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

SHA 加密
SHA 加密

SHA 加密工具

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具