替换语音服务

Cruzr 提供了较多的系统服务,其中就包含了语音服务。当 Cruzr 提供的语音服务功能无法满足需求时,开发者可以使用自己实现的服务替换原有的服务。

语音服务中语音识别、自然语言处理和语音合成三个模块可以单独替换。

添加依赖库

引入Cruzr SDK

Cruzr SDK 下载引入请参考 创建第一个机器人应用

引入Cruzr_speech_override.aar

语音服务替换需要额外的 SDK (cruzr_speech_override.aar),将下载好的依赖包放入 cruzr-libs目录下,并在 app/build.gradle 添加以下依赖:

dependencies {
    implementation(name: 'cruzr_speech_override', ext: 'aar')//语音服务替换 SDK
}

最后在 Android Studio 工具条上找到 Sync Project with Gradle Files按钮,点击执行同步操作即可。

注: 语音服务替换同时需要 cruzr.jar 和cruzr_speech_override.aar, 两者的依赖方式有区别。

配置替换规则

替换后的语音服务,在指定语言下,支持自动切换,在 app/src/main/res/xml 目录中创建 decision_list.xml 文件,添加覆盖配置,用于替换语音服务。当系统语言切换至指定语言时,语音服务指定模块将被替换, 使用开发者开发的语音服务模块。

<?xml version="1.0" encoding="utf-8"?>
<decision-list>
    <decision>
        <if>
            <!-- 选择在什么语言下替换语言服务 -->
            <language name="en-rUS" />
        </if>
        <then>
            <route-service>
                <!-- 替换语音合成 -->
                <parameter-group
                    package="你的应用包名"
                    service="speech.synthesis" />
                <!-- 替换语音识别 -->
                 <parameter-group
                    package="你的应用包名"
                    service="speech.recognition" />
                <!-- 替换语义理解 -->
                <parameter-group
                    package="你的应用包名"
                    service="speech.understanding" />      
            </route-service>
        </then>

        <if>
            <!-- 选择在什么语言下替换语言服务 -->
            <language name="zh-rCN" />
        </if>
        <then>
            <route-service>
                <!-- 替换语音合成 -->
                <parameter-group
                    package="你的应用包名"
                    service="speech.synthesis" />
            </route-service>
        </then>
    </decision>
</decision-list>

注: 语音服务三个模块可以独立替换其中的一个或者多个。其中语言配置必须按照 Android 资源标准语言格式配置。

开发自定义语音服务

实现服务

既然是要替换原有的语音服务,那么接下来,我们就要自己实现语音服务的功能。将服务替换规则配置好后,需要将配置中的服务进行具体实现。下面以语音播报为例:

public class YourSynthesisServices extends AbstractSynthesizer {

    private GoogleSpeaker googleSpeaker;

    public YourSynthesisServices() {
        googleSpeaker = new GoogleSpeaker();
    }

    @Override
    protected void startSynthesizing(SynthesisOption synthesisOption) {
        // 新的语音播报实现
        googleSpeaker.speak(synthesisOption.getInputText(), new TtsCallBack() {
            @Override
            public void onSpeakStart() {
                // 播报开始,通知进度变化
                reportSynthesizingProgress(new SynthesisProgress.Builder(SynthesisProgress.PROGRESS_BEGAN).build());
            }

            @Override
            public void onSpeaking() {
                // 播报中,通知进度变化
                reportSynthesizingProgress(new SynthesisProgress.Builder(SynthesisProgress.PROGRESS_PLAYING)// 播报状态
                        .setRemainingTimeMillis(3000)// 剩余播报时间
                        .setPlayProgress(50)// 当前播报进度
                        .build());
            }

            @Override
            public void onSpeakError(Exception e) {
                // 播报异常
                rejectSynthesizing(new SynthesisException(SynthesisException.CODE_INTERNAL_ERROR));
            }

            @Override
            public void onSpeakCompleted() {
                // 播报结束
                reportSynthesizingProgress(new SynthesisProgress.Builder(SynthesisProgress.PROGRESS_ENDED).build());

                resolveSynthesizing();
            }
        });
    }

    @Override
    protected void stopSynthesizing() {
        // 取消播报
        googleSpeaker.stop();
    }

    @Override
    public List<SpeakingVoice> getSpeakingVoiceList() {
        // 获取发音人信息
        SpeakingVoice speaker = new SpeakingVoice.Builder("google")
                .setLanguage("en")
                .build();
        return Collections.singletonList(speaker);
    }
}

声明服务

实现了语音服务后,我们还需要做最后一步,声明已实现的服务。在项目的 ApplicationonCreate() 将已实现的服务声明

public class FooApplication extends Application {

    @Override
    public void onCreate() {
        //TODO Initialization service
        ServiceModules.initialize(this);

        //TODO Declare speech recognition service
        ServiceModules.declare(
                Synthesizer.class,
                new ModuleCreator<Synthesizer>() {
                    @Override
                    public void createModule(Class<Synthesizer> aClass,
                                ModuleCreatedNotifier<Synthesizer> notifier) {
                        notifier.notifyModuleCreated(new FooServices());//提供服务实例
                    }
                }
        );
    }
}

实现你的服务

  1. 语音识别服务(ASR)
public class YourRecognitionService extends AbstractRecognizer {

    @Override
    protected void startRecognizing(RecognitionOption recognitionOption) {
        /** recognitionOption 参数说明:
         *      mode:识别模式,default:MODE_SINGLE
         *          1) MODE_SINGLE:单次识别;
         *          2) MODE_CONTINUOUS:连续识别;
         *          特别注意:
         *              若模式为 MODE_SINGLE, 识别结果应使用 resolveRecognizing 方法通知调用者识别结果,
         *              若模式为 MODE_CONTINUOUS, 识别结果应使用 reportRecognizingProgress 方法将结果通知给调用者.
         *      distanceRange:识别范围,可选;
         *      extension:扩展参数,可选;
         *      understandingOption:语义理解参数,可选(部分引擎同时包含识别和语义理解);
         */

        //TODO 你应该在此处添加识别的具体实现代码
        //your function code...

        //TODO 识别进度处理
        /** 1. 识别过程:如果你需要把识别过程通知给调用者,你可以调用该方法,并且你还可以在通知时携带一些信息.
        注意: 该方法与 startRecognizing() 对应,可多次执行
        持续识别模式下应使用此接口上报识别结果*/
        reportRecognizingProgress(
                new RecognitionProgress.Builder(RecognitionProgress.PROGRESS_BEGAN) //进度类型:
                        // 1) PROGRESS_BEGAN: 识别开始;
                        // 2) PROGRESS_RECOGNIZING:识别中;
                        // 3) PROGRESS_ENDED:识别结束;
                        // 4) PROGRESS_RECOGNITION_TEXT_RESULT:识别结果;
                        // 5) PROGRESS_UNDERSTANDING_RESULT:语义理解结果;
                        .setTextResult("RecognitionResult")  //识别结果,可选(MODE_CONTINUOUS 时,应填写识别结果在此处);
                        .setDecibel(50)                      //识别到的声音大小,可选;
                        .setUnderstandingResult(null)        //语义理解结果,可选(部分引擎同时包含识别和语义理解);
                        .build());

        //TODO 识别结果处理
        /** 2. 识别结束:当识别完成时,你可以调用该方法通知识别结果给调用者,.
         注意: 该方法与 startRecognizing() 对应,只生效一次,
         持续识别模式下如果使用此方法,本次识别将会结束*/
        resolveRecognizing(new RecognitionResult
                .Builder("RecognitionResult")              //识别结果(单次识别模式下必填)
                .setUnderstandingResult(null)              //语义理解结果,可选(部分引擎同时包含识别和语义理解);
                .build());

        //TODO 识别异常处理
        /** 3. 识别异常:当识别发生错误时,你可以调用该方法通知调用者,该方法只生效一次.
         注意: 该方法与 startRecognizing() 对应,只生效一次,
         持续识别模式下如果使用此方法,本次识别将会结束*/
        rejectRecognizing(new RecognitionException(
                RecognitionException.CODE_INTERNAL_ERROR,   //错误码,使用给定的
                123,                                        //定义自己的错误码,可选
                "Error Message")                            //错误信息,可选
        );
    }

    @Override
    protected void stopRecognizing() {
        //TODO 你应该在此处停止识别
        //your function code...
    }
}
  1. 语义理解服务(NLP)
public class YourUnderstandingService extends AbstractUnderstander {

    @Override
    protected void understand(UnderstandingOption understandingOption, Deferred<UnderstandingResult, UnderstandingException> deferred) {
        /** understandingOption 参数说明:
         *      InputText:输入的文本;
         *      SessionId:会话ID可选,可选;
         *      ContextList:上下文列表,可选;
         *      Parameters:参数集合,可选;
         *      TimeoutMillis:超时时间,可选;
         *      Version:版本,可选;
         */

        //TODO 你应该在此处添加语义理解的具体实现代码
        //your function code...

        //TODO 1. 语义理解完成处理,该方法与 understand() 对应,只生效一次
        deferred.resolve(new UnderstandingResult
                .Builder()
                .setFulfillmentList(null)       //语义理解返回的json内容,可选
                .setSpeechFulfillment(null)     //语义理解返回的数据,可选,TTS 默认播报该处内容
                .setLanguage("en-rUS")           //语言,可选
                .setIntent(null)                //语义理解返回的意图,分发skll时使用,可选
                .setSessionId("")               //SessionId,可选 (see UnderstandingOption.SessionId)
                .setSessionIncomplete(false)    //多轮对话结束标志,可选
                .setInputText("")               //输入的文本,可选 (see UnderstandingOption.InputText)
                .setContextList(null)           //上下文列表,可选  (see UnderstandingOption.ContextList)
                .setVersion("")                 //版本,可选 (see UnderstandingOption.Version)
                .setSource("")                  //平台,可选
                .build());

        //TODO 2. 语义理解异常处理,该方法与 understand() 对应,只生效一次
        deferred.reject(new UnderstandingException(
                UnderstandingException.CODE_INTERNAL_ERROR,     //错误码,使用给定的
                123,                                            //定义自己的错误码,可选
                "Error Message")                                //错误信息,可选
        );
    }
}
  1. 语音合成服务(TTS)
public class YourSynthesisService extends AbstractSynthesizer {

    @Override
    protected void startSynthesizing(SynthesisOption synthesisOption) {
        /** synthesisOption 参数说明:
         *      inputText:合成内容;
         *      speakingVoiceId:发音人;
         *      speakingSpeed: 速度 0.1~1~10, default:1;
         *      speakingVolume: 音量 0~100,default:0;
         */

        //TODO 你应该在此处添加语音合成的具体实现代码
        //your function code...

        //TODO 1. 合成过程通知,该方法与 startSynthesizing() 对应,可多次执行
        reportSynthesizingProgress(new SynthesisProgress
                .Builder(SynthesisProgress.PROGRESS_BEGAN)
                //进度通知类型
                // 1) PROGRESS_BEGAN 播报开始
                // 2) PROGRESS_PLAYING 播报中
                // 3) PROGRESS_ENDED 播报结束
                .setAudioBytes(new byte[]{})             //音频数据,可选
                .setPlayProgress(10)                     //进度百分比,可选
                .setRemainingTimeMillis(10 * 1000)       //剩余时间,可选
                .build());

        //TODO 2. 合成结束处理,该方法与 startSynthesizing() 对应,只生效一次
        resolveSynthesizing();

        //TODO 3. 合成异常处理,该方法与 startSynthesizing() 对应,只生效一次
        rejectSynthesizing(new SynthesisException(
                SynthesisException.CODE_INTERNAL_ERROR,  //错误码,使用给定的
                123,                                     //定义自己的错误码,可选
                "Error Message"));                       //错误信息,可选
    }

    @Override
    protected void stopSynthesizing() {
        //TODO 你应该在此处停止语音合成
        //your function code...
    }

    @Override
    public List<SpeakingVoice> getSpeakingVoiceList() {
        //TODO 如果你拥有多个发音人,你应该在此给出支持的发音人列表
        //your SpeakingVoiceList
        SpeakingVoice speakingVoice = new SpeakingVoice.Builder("1234") //发音人ID
                .setName("jack")                                        //发音人名称
                .setGender(0)                                           //发音人性别
                .setLanguage("en-rUS")                                  //发音人对应语种
                .build();
        return Collections.singletonList(speakingVoice);
    }
}

注意事项

  1. 首次安装时,设备需要重启,覆盖才能生效。
  2. 同一个系统中,请避免同时安装2个用于覆盖语音的 app
  3. 示例代码为针对google语音服务替换的代码:overriding-speech-service