慢牛系列五:用百度语音识别添加自选股

慢牛系列五:用百度语音识别添加自选股

开发慢牛股票app已经有很长一段时间,最近在考虑用什么方式添加自选股最方便?传统的做法是为用户专门开发一个键盘,字母或者数字的,帮助用户录入股票的简称或者编码,大多数app都是这么做的,下面是海通证券app为用户开发的键盘,多数券商是这么做的,这么做已经很不错了。

当然,我的想法没有什么太突出的,无外乎三种办法:

  • 第一种:从其他APP导入,比如从腾讯自选股导入,要获得腾讯自选股数据,需要用QQ号登陆QQ的Web端,一般不会有人愿意在我这里输入QQ和密码的。
  • 第二种:语音识别,上网搜了下,主要是百度和科大讯飞提供语音识别SDK,而且免费的,下载了demo,感觉不错,可以做。
  • 第三种:拍照,用户只需拍下来QQ自选股,上传图片,后台分析出个股,添加到用户的自选股里,上网搜了下图片识别的SDK,百度有,但是只有企业版,收费的,图片识别这块有个开源项目:Tessercat,出自惠普实验室,目前是谷歌在维护,试用了下,对简体中文的识别项目不太理想,如果想识别高的话,需要进行中文的识别训练,当然也尝试了下,是个体力活,所以这方面还是等百度开放他们的图片识别API在做吧,或者园里的大牛们有什么好的方案推荐下,多谢啦!

下面说说我使用百度语音识别的效果:

百度语音添加自选股

实现过程如下:

1.注册百度语音

注册网址:百度语音注册

开发者需要有个百度开发平台账号,还需要在应用中创建一个应用,并为应用开通语音识别服务,开通后获得api_key和secret_key。

下载SDK,我使用的是在线SDK。

在线SDK
我的应用是需要再有网络情况下使用的,所以选择在线的SDK。

2.引用文件

将SDK中的libs和res有两个文件夹拷贝到android项目对应的目录下,可以拷贝到android的主项目下,也可以专门建立子项目,放置这些文件。我单独建立了一个android项目,然后主项目引用子项目。

官方的手册里只是说明了如何在Eclipse里如何引用,可以参考这篇文章链接:
Eclipse中使用百度语音

我项目里用的是Android Studio,build工具是gradle,主要是gradle的设置。


apply plugin: 'com.android.library'

android {
	……
    sourceSets{
        main {
            jniLibs.srcDir(['libs']) // <-- Set your folder here!
        }
    }
	……
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
	……
}

说明:
SDK中包含jar和so文件,jar可以通过dependencies里设置引用:

compile fileTree(dir: 'libs', include: ['*.jar'])

so文需要再上面s件ourceSets中设置,如上面的main的设置:

jniLibs.srcDir(['libs'])

注意:还需要在libs下复制一个文件夹armeabi,重命名:armeabi-v7a,如果不加个这个文件夹,会报一个找不到BDVoiceRecognitionClient_MFE_V1类型的异常。

3.接入

语音主要有两种模式,对话框模式和API模式,我这里是说下对话框模式,API模式参考后续的代码。
关于如何调用语音对话框,百度语音官方的文档里有说明,因为我要在Reac Native里使用百度语音,所以需要写桥接代码,下面是在android端的代码,继承ViewGroupManager,在React Native里做了一个容器。


public class VoiseRecognitionManager extends ViewGroupManager<ReactViewGroup> {
    public static final int COMMAND_SHOW = 0;
    public static final int COMMAND_DISMISS = 1;
    private static final String CLASS_NAME = "VoiseRecognition";
    private ReactViewGroup view=null;
    private BaiduASRDigitalDialog mDialog=null;

    @Override
    public String getName() {
        return CLASS_NAME;
    }

    @Override
    protected ReactViewGroup createViewInstance(ThemedReactContext reactContext) {
        this.view= new ReactViewGroup(reactContext);
        return this.view;
    }

    @Override
    public @Nullable Map<String, Integer> getCommandsMap() {
        return MapBuilder.of(
                "show",
                COMMAND_SHOW,
                "dismiss",
                COMMAND_DISMISS);
    }

    @Override
    public void receiveCommand(
            ReactViewGroup view,
            int commandId,
            @Nullable ReadableArray config) {
            if(commandId==COMMAND_SHOW){
                this.showDialog(view.getContext(),config.getMap(0));
            }else if(commandId==COMMAND_DISMISS){
                this.dismiss();
            }
    }
    @ReactMethod
    public void showDialog(Context context,ReadableMap config) {
        if (mDialog != null) {
            mDialog.dismiss();
        }
        Bundle params=new Bundle();
        String api_key=config.getString("api_key");
        String secret_key=config.getString("secret_key");
        //设置开放平台 API Key
        params.putString(BaiduASRDigitalDialog.PARAM_API_KEY,api_key);
        //设置开放平台 Secret Key
        params.putString(BaiduASRDigitalDialog.PARAM_SECRET_KEY,secret_key);

        //设置识别领域:搜索、输入、地图、音乐……,可选。默认为输入。
        Object PARAM_PROP_VALUE= config.getInt("prop");
        params.putInt( BaiduASRDigitalDialog.PARAM_PROP, PARAM_PROP_VALUE==null?VoiceRecognitionConfig.PROP_INPUT:(Integer) PARAM_PROP_VALUE);

        //设置语种类型:中文普通话,中文粤语,英文,可选。默认为中文普通话
        Object LANGUAGE=config.getString("language");
        params.putString( BaiduASRDigitalDialog.PARAM_LANGUAGE,LANGUAGE==null?VoiceRecognitionConfig.LANGUAGE_CHINESE:LANGUAGE.toString());
        //如果需要语义解析,设置下方参数。领域为输入不支持
        Object PARAM_NLU_ENABLE=config.getBoolean("nlu_enable");
        params.putBoolean(BaiduASRDigitalDialog.PARAM_NLU_ENABLE,PARAM_NLU_ENABLE==null?false:(boolean)PARAM_NLU_ENABLE);
        // 设置对话框主题,可选。BaiduASRDigitalDialog 提供了蓝、暗、红、绿、橙四中颜色,每种颜
        //        色又分亮、暗两种色调。共 8 种主题,开发者可以按需选择,取值参考 BaiduASRDigitalDialog 中
        //        前缀为 THEME_的常量。默认为亮蓝色
        Object PARAM_DIALOG_THEME=config.getInt("dialog_theme");
        params.putInt(BaiduASRDigitalDialog.PARAM_DIALOG_THEME,PARAM_DIALOG_THEME==null?BaiduASRDigitalDialog.THEME_RED_DEEPBG:(Integer) PARAM_DIALOG_THEME);

        params.putBoolean( BaiduASRDigitalDialog.PARAM_START_TONE_ENABLE,true);
        params.putBoolean( BaiduASRDigitalDialog.PARAM_END_TONE_ENABLE,true);
        params.putBoolean( BaiduASRDigitalDialog.PARAM_TIPS_TONE_ENABLE,true);
        mDialog=new BaiduASRDigitalDialog(context,params);
        final ThemedReactContext reactContext=(ThemedReactContext)context;
        final ReactViewGroup tempView=this.view;
        DialogRecognitionListener mRecognitionListener=new DialogRecognitionListener(){

            @Override
            public void onResults(Bundle results){
                //此处处理识别结果,识别结果可能有多个,按置信度从高到低排列,第一个元素是置信度最高的结果。

                ArrayList<String> rs=results !=null?results
                        .getStringArrayList(RESULTS_RECOGNITION):null;
                if(rs!=null){
                    WritableArray params=Arguments.createArray();
                    for (String r : rs) {
                        params.pushString(r);
                    }
                    WritableMap data=Arguments.createMap();
                    data.putArray("result",params);
					//向JS端发送事件,附带识别结果
                    reactContext.getJSModule(RCTEventEmitter.class).receiveEvent(
                            tempView.getId(),
                            "topChange",
                            data);
                }
            }
        };
        mDialog.setDialogRecognitionListener(mRecognitionListener);
        mDialog.show();
    }
    @ReactMethod
    public void dismiss(){
        if(this.mDialog!=null)
            this.mDialog.dismiss();
    }
}


JS端代码


var requireNativeComponent = require('requireNativeComponent');
var React = require('React');
var View = require('View');
var PropTypes = require('ReactPropTypes');
var UIManager = require('UIManager');
var TouchableOpacity=require('TouchableOpacity');
var VOISE_REF = 'baiduVoise';
var params=require('./Params')

var BaiduVoise = React.createClass({
    getDefaultProps: function() {
      return {
        prop:params.PROP_FINANCE,
        language:params.LANGUAGE_CHINESE,
        dialog_theme:params.THEME_RED_DEEPBG,
        nlu_enable:false,
      };
    },
    propTypes: {
      ...View.propTypes,
      api_key: PropTypes.string,
      secret_key: PropTypes.string,
      prop: PropTypes.number,
      language: PropTypes.string,
      dialog_theme: PropTypes.number,
    },
    show: function(callback) {
      var config = {
        api_key: this.props.api_key,
        secret_key: this.props.secret_key,
        prop: this.props.prop,
        language: this.props.language,
        dialog_theme: this.props.dialog_theme,
        nlu_enable: this.props.nlu_enable
      };
      //向原生端发送事件,启动对话框
      UIManager.dispatchViewManagerCommand(
        React.findNodeHandle(this.refs[VOISE_REF]),
        UIManager.VoiseRecognition.Commands.show,
        [config]);
    },
    hide: function() {
	  //向原生端发送事件,隐藏对话框
      UIManager.dispatchViewManagerCommand(
        React.findNodeHandle(this.refs[VOISE_REF]),
        UIManager.VoiseRecognition.Commands.dismiss,
        []);
    },
    onReceive: function(e) {
      var result=e.nativeEvent.result;
      if(this.props.nlu_enable){
        var str=e.nativeEvent.result;
        var obj=JSON.parse(str);
        result=obj.item;
      }
      this.props.onReceive&&this.props.onReceive(result);
    },
    onPress:function (argument) {
      var me=this
      me.show();
    },
    render: function() {
      return (  
        <TouchableOpacity
            activeOpacity ={0.5}       
            underlayColor="#B5B5B5"
            onPress={this.onPress}>
            <VoiseRecognition
              ref={VOISE_REF}
              onChange={this.onReceive}
              style= {this.props.style}> 
              {this.props.children}
            </VoiseRecognition>
        </TouchableOpacity>
      );
    },

});

BaiduVoise.Params=params;

var VoiseRecognition = requireNativeComponent('VoiseRecognition', BaiduVoise, {
  nativeOnly: {
    onChange: true
  }
});


module.exports = BaiduVoise;


4.使用方法

/* @flow */
'use strict';

var React = require('react-native');
var {
  BaiduVoise,
  SpeechRecognizer
}=require('react-native-voise');

var {
  StyleSheet,
  View,
  Text
} = React;

var Component = React.createClass({
    getInitialState() {
        return { result:'' }
    },
    onReceive:function (results) {
        //results is a list ,the first one is the best result.
        this.setState((state)=>{
          state.result=results[0];
        });
    },
    render: function() {
        return (
            <View style={styles.container}>
                <Text>this.state.result</Text>
                <BaiduVoise 
                  ref={'BaiduVoise'}
                  style={styles.button}
                  api_key={'q0UcNM0glvjekMtBQNWzM92y'} //设置api_key和secret_key
                  secret_key={'8hRsMQCQGNdwqnyF8GkWBgr6WObZFT5l'} 
                  onReceive={this.onReceive}>      
                    <Text>点击,说话</Text>
                </BaiduVoise>
            </View>
        );
    }
});

var styles = StyleSheet.create({
    container:{
        flex:1
    },
    button:{
        height:50,
    }
});


module.exports = Component;

在React Native里,桥接原生组件是一件比较简单的事情,只要学一些原生的东西就能做了,主要是React Native给我们提供了平台和工具集,平台负责管理组件的布局,样式,行为,又建立了原生和JS端通信机制,让我们做Web的同学也可以做原生了,当然,特殊情况下还是需要完全原生的,大多数小而美的app,React Native是非常合适的。

听说微信要做应用号,将来微信估计也要做这样的一个平台,微信提供标准的UI组件,众多app开发者可以在微信的平台上通过js写出性能体验更好的app了,相比现在通过浏览器实现的企业号,体验肯定会更好。

如果有想体验百度语音的用户,可以下载慢牛APP的APK体验,点击右上角的放大镜,进入自选股添加页面:

关注慢牛的公众号:发送react,返回apk下载链接,apk大小8M,最好连接WiFi下载。

这个项目的代码已经发布到github上:https://github.com/hongyin163/react-native-voise

欢迎安装试用!

最后,欢迎园友提出好的想法,评论留名!谢谢!

posted @ 2016-01-19 05:43  OOLi  阅读(2238)  评论(11编辑  收藏  举报