16. AI功能

目前已开发的AI相关功能有:AI云服务、AI表盘

Important

公版发出去的项目工程,编译是无法正常使用AI相关功能的。需要先前往科大讯飞注册开通相关功能。在对应位置进行配置【APPID】【APISecret】【APIKey】。

16.1. AI云服务

Note

为兼容AI表盘功能,若App用到AI云服务功能的话,需要使用优化后的AI云服务功能(AIManager),不能使用优化前的AI云服务功能(AIServeManager)。如何区分AI云服务功能,是优化前还是优化后?优化后的AI云服务功能是集成在 AIManager.class 内,优化前是单独在 AIServeManager.class 实现

固件对应的功能:【科大讯飞AI】

App对应的功能:【设备】->【AI云服务】。

  • 初始化设备时,设备端告诉App是否支持AI云服务功能。

  • 设备端开始录音时,发送RCSP命令告诉App端开始录音。然后就开始传输录音数据。

  • 设备端停止录音,发送RCSP命令告诉App端停止录音,是否需要传输录音识别结果,是否需要传输语义识别结果,是否需要播放TTS。

  • App收到开始录音后,开始把接受到的实时录音数据进行Opus转码成PCM数据,转码PCM结果发送给科大讯飞服务器。App收到停止录音后,发送录音结束命令给科大讯飞服务器。科大讯飞先返回语言识别结果,再返回语音识别结果,再返回语义识别结果,App根据语义识别结果进行TTS转换。

  • App端把语音识别结果、语义识别结果,TTS播放状态发送给设备端。

16.1.1. 时序图

../../_images/ai_serve_single_timing_diagram.jpg

16.1.2. 科大讯飞配置

如不使用科大讯飞的SDK,改用其他平台的AISDK。可跳过这一步。

公版发出去的HealthAide项目,编译后是无法正常使用AI云服务功能。需要先前往科大讯飞注册开通【星火认知大模型-星火大模型V1.5】、【语音识别】、【语音合成】功能。

IflytekChatWrapper.class IflytekIatWrapper.class IflytekTtsWrapper.class 对应位置配置【APPID】【APISecret】【APIKey】。

//IflytekChatWrapper.class

public IflytekChatWrapper(Context context) {
    ......
    if (!TextUtils.isEmpty(BuildConfig.IFLYTEK_APP_ID)) {//TODO 需要注释掉这一行
        //TODO 请先在科大讯飞平台申请,请填写正确appId,appKey,apiSecret。否则会闪退
        final AiHelper.Params.Builder params = AiHelper.Params.builder()
                .appId("ffffffff"/**APPID**/)
                .apiKey("fffffffffffffffffffffffffffffff"/**APIKey**/)
                .apiSecret("ffffffffffffffffffffffffffffffff"/**APISecret**/)
                .workDir(rootFile.getPath());
        ......
    }
}
//IflytekIatWrapper.class

public IflytekIatWrapper(Context context) {
    //开log
    Setting.setShowLog(true);
    //初始化即创建语音配置对象
    if (!TextUtils.isEmpty(BuildConfig.IFLYTEK_APP_ID)) {//TODO 需要注释掉这一行
        //TODO 请先在科大讯飞平台申请,请填写正确appId。否则会闪退
        SpeechUtility.createUtility(context, SpeechConstant.APPID + "=" + "ffffffff"/**APPID**/);
    }
    //初始化识别无UI识别对象
    mSpeechRecognizer = SpeechRecognizer.createRecognizer(context, mIatInitListener);
}
//IflytekTtsWrapper.class

public IflytekTtsWrapper(Context context) {
    //开log
    Setting.setShowLog(true);
    //初始化即创建语音配置对象
    if (!TextUtils.isEmpty(BuildConfig.IFLYTEK_APP_ID)) {//TODO 需要注释掉这一行
        //TODO 请先在科大讯飞平台申请,请填写正确appId。否则会闪退
        SpeechUtility.createUtility(context, SpeechConstant.APPID + "=" + "ffffffff"/**APPID**/);
    }
    // 初始化合成对象
    mTts = SpeechSynthesizer.createSynthesizer(context, mTtsInitListener);
}

16.1.3. 使用示例

/**
* @ClassName: AICloudServerDemo
* @Description: AI云服务功能示例
* @Author: ZhangHuanMing
* @CreateDate: 2023/12/28 11:23
*/
public class AICloudServerDemo {
    private AIManager mAIManager;
    private final Observer<SessionInfo> mRecordStatusObserver = sessionInfo -> {
        if (sessionInfo != null) {
            switch (sessionInfo.getStatus()) {
                case SessionInfo.STATE_RECORD_START://开始录音
                case SessionInfo.STATE_RECORDING://录音中
                case SessionInfo.STATE_IDLE://默认状态
                case SessionInfo.STATE_RECORD_END://录音结束
                case SessionInfo.STATE_IAT_END://语音识别结束
                case SessionInfo.STATE_NLP_END://语义识别结束
                case SessionInfo.STATE_FAIL://异常
                    //获取当前对话消息
                    List<AICloudMessage>  aiCloudMessageList=  sessionInfo.getSessionMessageList();
                    for (AICloudMessage aiCloudMessage:aiCloudMessageList) {
                        //获取对话的类型。0;用户,1;AI
                        int itemType =  aiCloudMessage.getItemType();
                        //获取消息的数据实体
                        AICloudMessageEntity entity =  aiCloudMessage.getEntity();
                        entity.getAiCloudState();//此消息的语音识别状态
                        entity.getDevMac();//设备mac
                        entity.getId();//消息的唯一id
                        entity.getRevId();//消息对应的回复消息的id
                        entity.getRole();//消息的角色,0;用户,1;AI
                        entity.getText();//消息的文本
                        entity.getTime();//消息的时间
                        entity.getUid();//用户的唯一id
                    }
                    break;
            }
        }
    };
    /**
    * 初始化AIManager
    */
    @Test
    public void init(Context context){
        //Step1. 初始化
        AIManager.init(context, WatchManager.getInstance());
        mAIManager = AIManager.getInstance();
        //Step2. 监听对话状态。请根据使用场景选择observeForever或observe
        mAIManager.getAICloudServe().currentSessionMessageMLD.observeForever(mRecordStatusObserver);
    }

    /**
    * 获取录音开始时间
    */
    @Test
    public long getStartRecordTime(){
        return mAIManager.getAICloudServe().getStartRecordTime();
    }


    /**
    * 停止语音合成
    */
    @Test
    public void stopTTS(){
        mAIManager.getAICloudServe().stopTTS();
    }
}

16.1.4. 接入其他AI平台

请参考 IflytekAICloudServeHandler.classIflytekAIIatHandler.class 调用 BaseAICloudServeHandlerBaseAIIatHandler 的方法

//BaseAIIatHandler.class
//基础语音识别处理
public abstract class BaseAIIatHandler extends BaseAIHandler {
    /**
    * 需要实现
    */
    //设备断开
    abstract void onDeviceDisconnect();
    // 开始录音
    abstract void onRecordStart();
    // 收到录音数据(已解码成PCM格式)
    abstract void onRecordData(byte[] recordData);
    // 录音结束
    abstract void onRecordStop();
    // 录音取消
    abstract void onRecordCancel();


    /**
    * 需要调用
    */
    //语音识别异常标志位
    void setRecognizerFail(boolean recognizerFail);
    //网络异常标志位
    void setNetworkWrong(boolean networkWrong);
    //识别结果
    void onRecognizerResult(String iatString);
    //识别失败
    void onRecognizerFail();
}
//BaseAICloudServeHandler.class
//AI云服务处理流程
public abstract class BaseAICloudServeHandler extends BaseAIChatHandler {
    /**
    * 需要实现
    */
    /**
    * 停止TTS合成播放
    */
    abstract void onStopTTS();

    /**
    * 开始TTS合成播放
    */
    abstract void onStartTTS(String ttsText);

    /**
    * 开始对话
    *
    * @param userText 用户文本
    */
    abstract void onStartChat(String userText);

    /**
    * 处理对话状态
    */
    abstract void onHandlerSessionStatus(int status);

    /**
    * 需要调用
    */
    //添加语义文本对话内容
    protected void addNlpTTSMessage(String nlpText);
    //开始语义对话
    protected void onStartChat();
    //播放tts失败
    protected void onPlayTTSFail();
    //播放tts结束
    protected void onPlayTTSStop();
}

16.1.5. 是否支持AI云服务

如何判断设备是否支持AI云服务功能

WatchConfigure configure  mWatchManager.getWatchConfigure(device);
boolean isSupportAICloud = configure == null || configure.getFunctionOption() != null && configure.getFunctionOption().isSupportAICloud();;

16.2. AI表盘

固件对应的功能:【AI表盘】

App对应的功能:【设备】->【AI表盘】。

  • 初始化设备时,设备端告诉App是否支持AI表盘功能。

  • 设备进入AI表盘界面,发送RCSP命令告诉App端在使用AI表盘功能。

  • 设备端开始录音时,发送RCSP命令告诉App端开始录音。然后就开始传输录音数据。

  • 设备端停止录音,发送RCSP命令告诉App端停止录音。

  • App收到开始录音后,开始把接受到的实时录音数据进行Opus转码成PCM数据,转码PCM结果发送给科大讯飞服务器。App收到停止录音后,发送录音结束命令给科大讯飞服务器。科大讯飞先返回语音识别结果,再返回语音识别结果。

  • App同步语音识别结果给设备,设备显示语音识别结果。用户确认生成AI表盘

  • App调用文生图接口生成图片,根据屏幕尺寸裁剪生成缩略图发送给设备。用户确认安装表盘

  • App根据图片生成自定义表盘,给设备安装

16.2.1. 时序图

../../_images/ai_dial_single_timing_diagram.jpg

16.2.2. 科大讯飞配置

如不使用科大讯飞的SDK,改用其他平台的AISDK。可跳过这一步。**

公版发出去的HealthAide项目,编译后是无法正常使用AI表盘功能。需要先前往科大讯飞注册开通【星火认知大模型-图片生成】、【语音识别】、【语音合成】功能。

IflytekChatWrapper.class IflytekIatWrapper.class IflytekTextToImageWrapper.class 对应位置配置【APPID】【APISecret】【APIKey】。

//IflytekChatWrapper.class

public IflytekChatWrapper(Context context) {
    ......
    if (!TextUtils.isEmpty(BuildConfig.IFLYTEK_APP_ID)) {//TODO 需要注释掉这一行
        //TODO 请先在科大讯飞平台申请,请填写正确appId,appKey,apiSecret。否则会闪退
        final AiHelper.Params.Builder params = AiHelper.Params.builder()
                .appId("ffffffff"/**APPID**/)
                .apiKey("fffffffffffffffffffffffffffffff"/**APIKey**/)
                .apiSecret("ffffffffffffffffffffffffffffffff"/**APISecret**/)
                .workDir(rootFile.getPath());
        ......
    }
}
//IflytekIatWrapper.class

public IflytekIatWrapper(Context context) {
    //开log
    Setting.setShowLog(true);
    //初始化即创建语音配置对象
    if (!TextUtils.isEmpty(BuildConfig.IFLYTEK_APP_ID)) {//TODO 需要注释掉这一行
        //TODO 请先在科大讯飞平台申请,请填写正确appId。否则会闪退
        SpeechUtility.createUtility(context, SpeechConstant.APPID + "=" + "ffffffff"/**APPID**/);
    }
    //初始化识别无UI识别对象
    mSpeechRecognizer = SpeechRecognizer.createRecognizer(context, mIatInitListener);
}
//IflytekTextToImageWrapper.class

public void startChat(String usrInputText, TextToImageCallback callback) throws Exception {
        String appid = "ffffffff";/**APPID**/
        String apiSecret = "fffffffffffffffffffffffffffffff";/**APIKey**/
        String apiKey = "ffffffffffffffffffffffffffffffff";/**APISecret**/
        ......
}

16.2.3. 使用示例

/**
* @ClassName: AIDialDemo
* @Description: AI表盘功能示例
* @Author: ZhangHuanMing
* @CreateDate: 2023/12/28 11:39
*/
public class AIDialDemo {
    private AIManager mAIManager;
    /**
    * 初始化AIManager
    */
    @Test
    public void init(Context context){
        //Step1. 初始化
        AIManager.init(context, WatchManager.getInstance());
        mAIManager = AIManager.getInstance();
    }

    /**
    * 设置AI表盘风格
    */
    @Test
    public void setStyle(String style){
        mAIManager.getAIDial().setCurrentAIDialStyle(style);
    }
}

16.2.4. 接入其他AI平台

请参考 IflytekAIDialHandler.classIflytekAIIatHandler.class 调用 BaseAIDialHandlerBaseAIIatHandler 的方法

//BaseAIIatHandler.class
//基础语音识别处理
public abstract class BaseAIIatHandler extends BaseAIHandler {
    /**
    * 需要实现
    */
    //设备断开
    abstract void onDeviceDisconnect();
    // 开始录音
    abstract void onRecordStart();
    // 收到录音数据(已解码成PCM格式)
    abstract void onRecordData(byte[] recordData);
    // 录音结束
    abstract void onRecordStop();
    // 录音取消
    abstract void onRecordCancel();


    /**
    * 需要调用
    */
    //语音识别异常标志位
    void setRecognizerFail(boolean recognizerFail);
    //网络异常标志位
    void setNetworkWrong(boolean networkWrong);
    //识别结果
    void onRecognizerResult(String iatString);
    //识别失败
    void onRecognizerFail();
}
//BaseAIDialHandler.class
//AI表盘处理流程
public abstract class BaseAIDialHandler extends BaseAIChatHandler {
    /**
    * 开始对话(文生图),需要实现
    */
    abstract void onStartAIChat(String userText);
    /**
    * 处理AI图片(文生图),需要调用
    * @param srcImagePath 本地图片路径
    */
    protected void handleAIChatImage(String srcImagePath);
}

16.2.5. 是否支持AI表盘

如何判断设备是否支持AI表盘功能

WatchConfigure configure  mWatchManager.getWatchConfigure(device);
boolean isSupportAIDial = configure == null || configure.getFunctionOption() != null && configure.getFunctionOption().isSupportAIDial();;

16.2.6. 本地测试AI表盘

在测试过程中为避免浪费服务器的文生图资源,AI表盘有本地测试功能。在发布正式版时,请关闭。

在打开App时,将assert/aidial 的资源拷贝至/Android/data/com.jieli.healthaide/files/aidial。(从1到10轮询选择一张图片进行AI表盘生成)

关闭本地测试功能:

//HealthConstant.class
public class HealthConstant {
    .....
    //测试AI表盘功能
    public final static boolean TEST_AI_DIAL_FUNCTION = false;
    .....
}

16.2.7. AI表盘缩略图

目前AI表盘的缩略图文件大小默认为240*240,设备端可在进入AI表盘界面时推送缩略图大小。如需修改默认缩略图大小,请修改 AIDialWrapper.java 的thumbHeight和thumbWidth属性。

//AIDialWrapper.class
private final OnWatchCallback mWatchCallback = new OnWatchCallback() {
    @Override
    public void onRcspCommand(BluetoothDevice device, CommandBase command) {
        super.onRcspCommand(device, command);
        if (command.getId() == Command.CMD_AI_OPERATE) {
            AIOperateCmd aiOperateCmd = (AIOperateCmd) command;
            AIOperateParam param = aiOperateCmd.getParam();
            if (param.getOp() == AttrAndFunCode.AI_OP_AI_DIAL) {
                switch (param.getFlag()) {
                    case AttrAndFunCode.AI_DIAL_OP_UI://设备通知AppAI表盘界面变化
                        Integer state = param.getAiDialFunUIState();
                        Integer scaleZoomHeight = param.getScaleZoomHeight();
                        Integer scaleZoomWidth = param.getScaleZoomWidth();
                        if (state != null) {
                            onDevNotifyAIDialUIChange(state);
                        }
                        if (scaleZoomHeight != null) {
                            thumbHeight = scaleZoomHeight;
                        } else {
                            thumbHeight = 240;
                        }
                        if (scaleZoomWidth != null) {
                            thumbWidth = scaleZoomWidth;
                        } else {
                            thumbWidth = 240;
                        }
                        break;

                }
                aiOperateCmd.setParam(new AIOperateParam.AIOperateResultParam(0));
                aiOperateCmd.setStatus(StateCode.STATUS_SUCCESS);
                mWatchManager.sendCommandResponse(mWatchManager.getConnectedDevice(), aiOperateCmd, null);
            }
        }
    }
};

16.3. 代码架构

../../_images/ai_code_architecture.png