讯飞语音——离线命令词识别

离线命令词识别

  • 效果图

  • 示例源码

地址:http://download.csdn.net/detail/q4878802/9023825

步骤:

1. 下载SDK

前面文章有,就不在复述了。这里要选择离线命令词的服务以后,重新加载,因为需要下载离线命令词识别的资源文件

地址:http://blog.csdn.net/q4878802/article/details/47762169

2. 集成方法

前面文章有,就不在复述了。

地址:http://blog.csdn.net/q4878802/article/details/47778629

3. 正题,开始集成

1. 添加权限

这里用到的唤醒功能不是所有的权限都用到的,具体用到了哪些权限,可以看上面的链接,用到哪写权限就加哪些权限,这个为了快速方便测试,把讯飞用到的权限都加上了。

<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

2. 初始化appid

我是将appid的初始化放在的Applicaiton下,具体可以下载源码

// 应用程序入口处调用,避免手机内存过小,杀死后台进程后通过历史intent进入Activity造成SpeechUtility对象为null
// 如在Application中调用初始化,需要在Mainifest中注册该Applicaiton
// 注意:此接口在非主进程调用会返回null对象,如需在非主进程使用语音功能,请增加参数:SpeechConstant.FORCE_LOGIN+"=true"
// 参数间使用“,”分隔。
// 设置你申请的应用appid
StringBuffer param = new StringBuffer();
param.append("appid=55d33f09");
param.append(",");
param.append(SpeechConstant.ENGINE_MODE + "=" + SpeechConstant.MODE_MSC);
// param.append(",");
// param.append(SpeechConstant.FORCE_LOGIN + "=true");
SpeechUtility.createUtility(InitKqwSpeech.this, param.toString()); 

3. 工具类

package com.example.kqwspeechdemo2.utils;

import java.io.InputStream;
import android.content.Context;
import android.util.Log;

/**
 * 功能性函数扩展类
 *
 * @author kongqw
 *
 */
public class FucUtil {

    // Log标签
    private static final String TAG = "FucUtil";

    /**
     * 读取asset目录下文件内容
     *
     * @return content
     */
    public static String readFile(Context mContext, String file, String code) {
        int len = 0;
        byte[] buf = null;
        String result = "";
        try {
            InputStream in = mContext.getAssets().open(file);
            len = in.available();
            buf = new byte[len];
            in.read(buf, 0, len);

            result = new String(buf, code);
        } catch (Exception e) {
            Log.e(TAG, e.toString());
            e.printStackTrace();
        }
        return result;
    }

}

4. 构建语法文件的工具类

package com.example.kqwspeechdemo2.engine;

import com.example.kqwspeechdemo2.utils.FucUtil;
import com.iflytek.cloud.ErrorCode;
import com.iflytek.cloud.GrammarListener;
import com.iflytek.cloud.InitListener;
import com.iflytek.cloud.SpeechConstant;
import com.iflytek.cloud.SpeechError;
import com.iflytek.cloud.SpeechRecognizer;
import com.iflytek.cloud.util.ResourceUtil;
import com.iflytek.cloud.util.ResourceUtil.RESOURCE_TYPE;

import android.content.Context;
import android.os.Environment;
import android.util.Log;
import android.widget.Toast;

/**
 * 构建离线命令词语法
 *
 * @author kongqw
 *
 */
public abstract class BuildLocalGrammar {

    /**
     * 构建语法的回调
     *
     * @param errMsg
     *            null 构造成功
     */
    public abstract void result(String errMsg, String grammarId);

    // Log标签
    private static final String TAG = "BuildLocalGrammar";

    public static final String GRAMMAR_PATH = Environment.getExternalStorageDirectory().getAbsolutePath() + "/msc/test";
    // 上下文
    private Context mContext;
    // 语音识别对象
    private SpeechRecognizer mAsr;

    public BuildLocalGrammar(Context context) {
        mContext = context;

        // 初始化识别对象
        mAsr = SpeechRecognizer.createRecognizer(context, new InitListener() {

            @Override
            public void onInit(int code) {
                Log.d(TAG, "SpeechRecognizer init() code = " + code);
                if (code != ErrorCode.SUCCESS) {
                    result(code + "", null);
                    Log.d(TAG, "初始化失败,错误码:" + code);
                    Toast.makeText(mContext, "初始化失败,错误码:" + code, Toast.LENGTH_SHORT).show();
                }
            }
        });
    };

    /**
     * 构建语法
     *
     * @return
     */
    public void buildLocalGrammar() {
        try {
            /*
             * TODO 如果你要在程序里维护bnf文件,可以在这里加上你维护的一些逻辑
             * 如果不嫌麻烦,要一直改bnf文件,这里的代码可以不用动,不过我个人不建议一直手动修改bnf文件
             * ,内容多了以后很容易出错,不好找Bug,建议每次改之前先备份。 建议用程序维护bnf文件。
             */

            /*
             * 构建语法
             */
            String mContent;// 语法、词典临时变量
            String mLocalGrammar = FucUtil.readFile(mContext, "kqw.bnf", "utf-8");
            mContent = new String(mLocalGrammar);
            mAsr.setParameter(SpeechConstant.PARAMS, null);
            // 设置文本编码格式
            mAsr.setParameter(SpeechConstant.TEXT_ENCODING, "utf-8");
            // 设置引擎类型
            mAsr.setParameter(SpeechConstant.ENGINE_TYPE, SpeechConstant.TYPE_LOCAL);
            // 设置语法构建路径
            mAsr.setParameter(ResourceUtil.GRM_BUILD_PATH, GRAMMAR_PATH);
            // 使用8k音频的时候请解开注释
            // mAsr.setParameter(SpeechConstant.SAMPLE_RATE, "8000");
            // 设置资源路径
            mAsr.setParameter(ResourceUtil.ASR_RES_PATH, getResourcePath());
            // 构建语法
            int ret = mAsr.buildGrammar("bnf", mContent, new GrammarListener() {

                @Override
                public void onBuildFinish(String grammarId, SpeechError error) {
                    if (error == null) {
                        Log.d(TAG, "语法构建成功");
                        result(null, grammarId);
                    } else {
                        Log.d(TAG, "语法构建失败,错误码:" + error.getErrorCode());
                        result(error.getErrorCode() + "", grammarId);
                    }
                }
            });
            if (ret != ErrorCode.SUCCESS) {
                Log.d(TAG, "语法构建失败,错误码:" + ret);
                result(ret + "", null);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // 获取识别资源路径
    private String getResourcePath() {
        StringBuffer tempBuffer = new StringBuffer();
        // 识别通用资源
        tempBuffer.append(ResourceUtil.generateResourcePath(mContext, RESOURCE_TYPE.assets, "asr/common.jet"));
        // 识别8k资源-使用8k的时候请解开注释
        // tempBuffer.append(";");
        // tempBuffer.append(ResourceUtil.generateResourcePath(this,
        // RESOURCE_TYPE.assets, "asr/common_8k.jet"));
        return tempBuffer.toString();
    }

}

5. 命令词识别工具类

package com.example.kqwspeechdemo2.engine;

import com.iflytek.cloud.ErrorCode;
import com.iflytek.cloud.InitListener;
import com.iflytek.cloud.RecognizerListener;
import com.iflytek.cloud.RecognizerResult;
import com.iflytek.cloud.SpeechConstant;
import com.iflytek.cloud.SpeechError;
import com.iflytek.cloud.SpeechRecognizer;
import com.iflytek.cloud.util.ResourceUtil;
import com.iflytek.cloud.util.ResourceUtil.RESOURCE_TYPE;

import android.content.Context;
import android.os.Bundle;
import android.os.Environment;
import android.util.Log;
import android.widget.Toast;

/**
 * 命令词识别
 *
 * @author kongqw
 *
 */
public abstract class KqwSpeechRecognizer {
    /**
     * 初始化的回调
     *
     * @param flag
     *            true 初始化成功 false 初始化失败
     */
    public abstract void initListener(boolean flag);

    public abstract void resultData(String data);

    public abstract void speechLog(String log);

    // Log标签
    private static final String TAG = "KqwLocalCommandRecognizer";

    public static final String GRAMMAR_PATH = Environment.getExternalStorageDirectory().getAbsolutePath() + "/msc/test";
    // 上下文
    private Context mContext;
    // 语音识别对象
    private SpeechRecognizer mAsr;

    public KqwSpeechRecognizer(Context context) {
        mContext = context;

        // 初始化识别对象
        mAsr = SpeechRecognizer.createRecognizer(context, new InitListener() {

            @Override
            public void onInit(int code) {
                Log.d(TAG, "SpeechRecognizer init() code = " + code);
                if (code != ErrorCode.SUCCESS) {
                    initListener(false);
                    Toast.makeText(mContext, "初始化失败,错误码:" + code, Toast.LENGTH_SHORT).show();
                } else {
                    initListener(true);
                }
            }
        });

    }

    /**
     * 参数设置
     */
    public void setParam() {
        // 清空参数
        mAsr.setParameter(SpeechConstant.PARAMS, null);
        // 设置识别引擎 本地引擎
        mAsr.setParameter(SpeechConstant.ENGINE_TYPE, SpeechConstant.TYPE_LOCAL);
        // mAsr.setParameter(SpeechConstant.ENGINE_TYPE,
        // SpeechConstant.TYPE_MIX);
        // mAsr.setParameter(SpeechConstant.ENGINE_TYPE, "mixed");
        // // 设置本地识别资源
        mAsr.setParameter(ResourceUtil.ASR_RES_PATH, getResourcePath());
        // 设置语法构建路径
        mAsr.setParameter(ResourceUtil.GRM_BUILD_PATH, GRAMMAR_PATH);
        // 设置返回结果格式
        mAsr.setParameter(SpeechConstant.RESULT_TYPE, "json");
        // 设置本地识别使用语法id
        mAsr.setParameter(SpeechConstant.LOCAL_GRAMMAR, "kqw");
        // 设置识别的门限值
        mAsr.setParameter(SpeechConstant.MIXED_THRESHOLD, "60");
        // 使用8k音频的时候请解开注释
        // mAsr.setParameter(SpeechConstant.SAMPLE_RATE, "8000");
        mAsr.setParameter(SpeechConstant.DOMAIN, "iat");
        mAsr.setParameter(SpeechConstant.NLP_VERSION, "2.0");
        mAsr.setParameter("asr_sch", "1");
        // mAsr.setParameter(SpeechConstant.RESULT_TYPE, "json");
    }

    // 获取识别资源路径
    private String getResourcePath() {
        StringBuffer tempBuffer = new StringBuffer();
        // 识别通用资源
        tempBuffer.append(ResourceUtil.generateResourcePath(mContext, RESOURCE_TYPE.assets, "asr/common.jet"));
        // 识别8k资源-使用8k的时候请解开注释
        // tempBuffer.append(";");
        // tempBuffer.append(ResourceUtil.generateResourcePath(this,
        // RESOURCE_TYPE.assets, "asr/common_8k.jet"));
        return tempBuffer.toString();
    }

    int ret = 0;// 函数调用返回值

    /**
     * 开始识别
     */
    public void startListening() {
        // 设置参数
        setParam();

        ret = mAsr.startListening(mRecognizerListener);
        if (ret != ErrorCode.SUCCESS) {
            Log.i(TAG, "识别失败,错误码: " + ret);
        }
    }

    /**
     * 识别监听器。
     */
    private RecognizerListener mRecognizerListener = new RecognizerListener() {

        StringBuffer stringBuffer = new StringBuffer();

        public void onVolumeChanged(int volume) {
            Log.i(TAG, "当前正在说话,音量大小:" + volume);
            speechLog("当前正在说话,音量大小:" + volume);
        }

        @Override
        public void onResult(final RecognizerResult result, boolean isLast) {
            /*
             * TODO 拼接返回的数据
             *
             * 这里返回的是Json数据,具体返回的是离线名命令词返回的Json还是语义返回的Json,需要做判断以后在对数据数据进行拼接
             */
            stringBuffer.append(result.getResultString()).append("\n\n");
            // isLast为true的时候,表示一句话说完,将拼接后的完整的一句话返回
            if (isLast) {
                // 数据回调
                resultData(stringBuffer.toString());
            }
        }

        @Override
        public void onEndOfSpeech() {
            Log.i(TAG, "结束说话");
            speechLog("结束说话");
        }

        @Override
        public void onBeginOfSpeech() {
            stringBuffer.delete(0, stringBuffer.length());
            Log.i(TAG, "开始说话");
            speechLog("开始说话");
        }

        @Override
        public void onError(SpeechError error) {
            Log.i(TAG, "error = " + error.getErrorCode());
            if (error.getErrorCode() == 20005) {
                // 本地命令词没有识别,也没有请求到网络
                resultData("没有构建的语法");
                speechLog("没有构建的语法");
                /*
                 * TODO
                 * 当网络正常的情况下是不会回调20005的错误,只有当本地命令词识别不匹配,网络请求也失败的情况下,会返回20005
                 * 这里可以自己再做处理,例如回复“没有听清”等回复
                 */
            } else {
                /*
                 * TODO
                 * 其他错误有很多,需要具体问题具体分析,正常在程序没有错误的情况下,只会回调一个没有检测到说话的错误,没记错的话错误码是10118
                 */
            }
        }

        @Override
        public void onEvent(int eventType, int arg1, int arg2, Bundle obj) {
            Log.i(TAG, "eventType = " + eventType);
        }

    };

}

这里的识别引擎设置的是SpeechConstant.TYPE_LOCAL,这种是本地识别引擎,只走本地识别,不走网络,如果换成SpeechConstant.TYPE_MIX,就是混合引擎,这种引擎方式,当本地没有识别到语法,返回20005错误码的时候,会直接请求语义接口,如果你语义开通了对应的场景,会走网络把你的语音转为语义,如果没有开通对应的场景,会把语音转为文字。

6. 测试类

package com.example.kqwspeechdemo2;

import com.example.kqwspeechdemo2.engine.BuildLocalGrammar;
import com.example.kqwspeechdemo2.engine.KqwSpeechRecognizer;

import android.app.Activity;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;

public class MainActivity extends Activity {

    private TextView mTvResult;
    private TextView mTvLog;
    private BuildLocalGrammar buildLocalGrammar;
    private KqwSpeechRecognizer kqwSpeechRecognizer;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mTvResult = (TextView) findViewById(R.id.tv_result);
        mTvLog = (TextView) findViewById(R.id.tv_log);

        /**
         * 初始化本地语法构造器
         */
        buildLocalGrammar = new BuildLocalGrammar(this) {

            @Override
            public void result(String errMsg, String grammarId) {
                // errMsg为null 构造成功
                if (TextUtils.isEmpty(errMsg)) {
                    Toast.makeText(MainActivity.this, "构造成功", Toast.LENGTH_SHORT).show();
                } else {
                    Toast.makeText(MainActivity.this, "构造失败", Toast.LENGTH_SHORT).show();
                }
            }
        };

        /**
         * 初始化离线命令词识别器
         */
        kqwSpeechRecognizer = new KqwSpeechRecognizer(this) {

            @Override
            public void speechLog(String log) {
                // 录音Log信息的回调
                mTvLog.setText(log);
            }

            @Override
            public void resultData(String data) {
                // 是识别结果的回调
                mTvResult.setText(data);
            }

            @Override
            public void initListener(boolean flag) {
                // 初始化的回调
                if (flag) {
                    Toast.makeText(MainActivity.this, "初始化成功", Toast.LENGTH_SHORT).show();
                } else {
                    Toast.makeText(MainActivity.this, "初始化失败", Toast.LENGTH_SHORT).show();
                }
            }
        };

        /**
         * 构造本地语法文件,只有语法文件有变化的时候构造成功一次即可,不用每次都构造
         */
        buildLocalGrammar.buildLocalGrammar();

    }

    /**
     * 开始识别按钮
     *
     * @param view
     */
    public void start(View view) {
        mTvResult.setText(null);
        // 开始识别
        kqwSpeechRecognizer.startListening();
    }

}

7. 界面布局

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.example.kqwspeechdemo2.MainActivity" >

    <Button
        android:id="@+id/bt_start"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:onClick="start"
        android:text="开始录音" />

    <TextView
        android:id="@+id/tv_log"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_above="@id/bt_start"
        android:gravity="center"
        android:text="录音信息" />

    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_above="@id/tv_log" >

        <TextView
            android:id="@+id/tv_result"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="返回结果" />
    </ScrollView>

</RelativeLayout>

8. BNF语法文件

#BNF+IAT 1.0 UTF-8;
!grammar kqw;
!slot <contact>;
!slot <callPre>;
!slot <callPhone>;
!slot <callTo>;
!slot <go>;
!slot <position>;

!slot <move>;

!start <callStart>;
<callStart>:
[<callPre>][<callTo>]<contact><callPhone>
|[<callPre>]<callPhone>[<callTo>]<contact>
|[<callPre>]<callPhone>

|<move>

|<go><position>;

<contact>:庆威|小孔|孔庆威;
<callPre>:我要|我想|我想要;
<callPhone>:打电话;
<callTo>:给;

<move>:前进|后退|左转|右转;

<go>:去|到;
<position>:厨房|卧室|阳台|客厅;

在构建语法的时候,我们不是必要在assets目录下创建一个xxx.bnf文件,构建的时候我们只要能够拿到满足BNF语法文件的字符串就行,至于这个文件内容,你存在哪都无所谓,在程序里写死、存sp、数据库、自己程序维护都OK,只要满足BNF的语法就行。

BNF语法开发指南

下载地址:http://download.csdn.net/detail/q4878802/9023791

最后

  1. 如果你直接用我的Demo,我用的是测试版的离线包,只有35天的试用期,而且装机量只有3个,如果大家都用,很可能是不能正常运行的
  2. 如果是参考我的demo自己写一个,千万不要忘记替换appid和资源文件。

版权声明:本文为博主原创文章,未经博主允许不得转载。所有文章,任何一个技术点,无论是原创还是转载,都是在程序中使用过或者Demo测试过才发表!博主QQ:4878802。

时间: 2024-07-29 22:12:26

讯飞语音——离线命令词识别的相关文章

android 开发:讯飞的离线命令识别器官方demo使用及demo下载

场景:使用本地构建语法,离线识别命令词. 修改文件AsrDemo.java mLocalGrammar  修改为你自己的语法 mAsr.setParameter(SpeechConstant.GRAMMAR_LIST, "语法名称"); demo里面使用的是无ui界面的命令词识别,有ui界面的待续开发. 下载地址:http://pan.baidu.com/s/1o6DnAx8 

讯飞离线命令词

1.下载讯飞语音离线命令词功能sdk 2.运行讯飞离线命令词demo 可能出现的问题: 1.app_id没有填写正确导致,创建SpeechRecognize 对象时得到null 和 构建 grammar 2.sdk与资源文件不一致,在语法构建时会出现,服务未授权 3.新建一个项目,将demo添加在里面演示,能正常进行离线语音和离线命令词的功能使用 新建一个项目,将demo中的lib和资源添加到新项目,编写离线命令词代码,能正常使用离线语音和离线命令词功能 将 lib 和 资源  和功能代码集成到

(原创)用讯飞语音实现人机交互的功能

目前在做一款车载的项目,其中有一个需求是在开车的时候实现人与手机的对话,全过程不需要用手,只用语音操控. 这个就类似于人与机器人的对话,机器人在后台一直待命,用户说话 机器人做出对应的反映. 但由于用户手机电源的宝贵性,又不能让用户一直开着录音监听,这样很耗费资源.因此使用了讯飞语音提供的唤醒功能. 具体怎么做呢? 看一张流程图吧:这张流程图使用了讯飞的大部分技术(语音唤醒.语音唤醒+命令词识别.语义识别.语音合成),不废话,看图 流程图已经写的很清晰了,简单介绍下 在程序启动的时候先启动唤醒,

基于讯飞语音API应用开发之——离线词典构建

最近实习在做一个跟语音相关的项目,就在度娘上搜索了很多关于语音的API,顺藤摸瓜找到了科大讯飞,虽然度娘自家也有语音识别.语义理解这块,但感觉应该不是很好用,毕竟之前用过百度地图的API,有问题也找不到人帮忙解决(地图开发者群里都是潜水的)...不得不说,科大讯飞在语音这块尤其是中文识别方面做的真心不错,而且Android还支持离线识别. 讯飞官方给的文档内容很详细,在这我就不赘述了.在开发中,由于一些原因需要用到离线识别这块,就学习了一下.讯飞离线识别只支持Android系统,使用时需要安装讯

Android讯飞语音云语音听写学习

讯飞语音云语音听写学习         这几天两个舍友都买了iPhone 6S,玩起了"Hey, Siri",我依旧对我的Nexus 5喊着"OK,Google".但种种原因,国内的"OK,Google"并不能展示出他的全部威力,于是上网搜索国内Android平台的语音助手,个人觉得评价最好的是讯飞的--灵犀语音助手.其实讯飞语音云平台早就注册过了,并下载了相应的SDK,只是没仔细研究.今天突然想好好学习一下,以方便以后集成到自己开发的APP中,

讯飞语音接口注册

1.首先申请账号 http://open.voicecloud.cn/ 一个邮箱就够了,验证邮箱后,会得到一个appid 2.然后登陆账号,到“我的语音云”---创建一个新的应用 填写应用名称iflyreminder,后选择分类,然后描述应用大致要实现的功能,选择平台,这里选择Android. 当然也有IOS WP8 JAVA FLASH WINDOWS LINUX等平台.提交后就建立成功了. 3.在“我的语音云”---我的应用下,选择刚刚创建的iflyreminder,点“立即开通服务”(默认

讯飞语音云、讯飞输入法四周年生日Party圆满举办

2014年10月28日下午,以"语你同行 音你精彩"为主题的讯飞语音云.讯飞输入法四周年生日会在北京3W咖啡成功举办.本场活动吸引了开发者.输入法粉丝.媒体朋友以及语音云合作伙伴等各界友人参加,活动现场座无虚席,气氛热烈. 四年前的今天,全球首个面向开发者的智能语音交互平台"讯飞语音云"正式发布,讯飞输入法也横空出世.经过四年时间的洗礼,讯飞语音云升级3.0,讯飞输入法也迅速成长积累1.7亿用户,成为移动互联网的拳头产品. 本场活动不仅准备抽奖互动和蛋糕盛宴,还带来

讯飞语音——唤醒

讯飞语音唤醒 唤醒功能,顾名思义,通过语音,唤醒服务,做我们想做的事情. 效果图(开启应用后说讯飞语音或者讯飞语点唤醒) 源码下载 地址:http://download.csdn.net/detail/q4878802/9023213 步骤 1. 创建应用,开通服务 地址:http://blog.csdn.net/q4878802/article/details/47762169 2. 下载SDK 我们要使用的是讯飞的付费功能,选择唤醒服务,点击下载以后,会提示没有购买.点击"购买服务"

一百元的智能家居——Asp.Net Mvc Api+讯飞语音+Android+Arduino

大半夜的,先说些废话提提神 如今智能家居已经不再停留在概念阶段,高大上的科技公司都已经推出了自己的部分或全套的智能家居解决方案,不过就目前的现状而言,大多还停留在展厅阶段,还没有广泛的推广起来,有人说最大的问题是标准不统一云云,但在我看来,最大的问题在于两个方面,一个是价格,一个是操作的简便性,技术上的问题并不是阻碍智能家居推广的核心因素. 再来说说最近很火很惹人爱的微软小娜,Cortana.本人作为微软的死忠,作为一名靠.Net混饭的屌丝程序男,自然是有一部撸妹的,并且在小娜推送当天更新了手机