如何让你的应用言"听"计从

背景:

  公司每年会给出两天时间让大家各自提出、准备、实现和展示自己的idea。源于这次活动,对于语音识别做了一番了解,并对一些工具和API做了一些实战调用。最后选择调用一个神奇的js库实现了语音识别和指令识别,融入了项目当中。

  Demo的视频地址:http://pan.baidu.com/s/1c1OsQYk  提取密码:229p

  起初,准备使用百度语音API实现语音识别,但是面临下面的困扰:

    1.REST方式,百度推出的基于跨平台的REST 方式的语音识别,但是API文档中明确说明,该方式只支持音频文件上传的方式

    2.Android平台,搭建好android环境并导入项目后,Demo程序可以运行,而且导出的apk文件也可以在android真机上运行。但是我们的产品是web应用,要将android平台的代码抽出来放进产品,从时间上来说,显然不够

  后来,我们找到了一个js库——annyang。

    该库是一个很小的JavaScript库,可以让你的访客用语音命令来控制你的网站。annyang支持多国语言,没有依赖性,重量仅为2KB,可以免费使用。

所以今天的主要内容分为以下三部分

百度语音Android平台

1.导入项目代码  

  在上篇《玩转百度语音识别,就是这么简单》我们已经搭建了android的环境,并导入百度给出的Android Demo代码。但是之前我们只看到了一个名为"Speech Recorder"的应用,后来我再次启动模拟器的时候,除了"Speech Recorder"以外还有一个"百度语音示例(2.x)"的应用。

  在模拟器中似乎没有办法访问到麦克风,于是安装到android机上测试。当然首先你需要将代码Export为apk文件,具体操作网上很多。

  

2.安装过程和测试过程

  安装完成

  应用界面:

  语音输入及识别结果

                                        (太不要脸了,但是说的都是大实话^^)

  至此,亲测android平台可用,而且从识别结果来看,还是蛮准确的。

百度语音REST API

  在上篇中我们也对REST API的方式做了介绍并亲测上传录制好的音频文件再调用API是能够返回结果的。REST API的调用方式跨平台,轻便好用,但是需要录好音频文件上传后才能得到语音识别的结果,显得不是很方便。

1.设计思路

  • 绘制一个界面包含开始和结束用于控制语音输入
  • 点击开始时,开始调用麦克风
  • 添加线程监控麦克风,将麦克风输入的内容存储为指定音频格式的文件
  • 当点击停止按钮时,调用语音识别的REST API,读取刚刚生成的音频文件
  • 请求远程识别服务,返回音频文件识别后的结果

有了以上思路,具体实现的代码如下

package com.baidu.speech.serviceapi;

import javax.swing.*;
import javax.xml.bind.DatatypeConverter;

import org.json.JSONObject;

import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;

import javax.sound.sampled.*;

public class AudioUI extends JFrame {

	AudioFormat audioFormat;
	TargetDataLine targetDataLine;

	final JButton captureBtn = new JButton("Capture");
	final JButton stopBtn = new JButton("Stop");

	final JPanel btnPanel = new JPanel();
	final ButtonGroup btnGroup = new ButtonGroup();
	final JRadioButton aifcBtn = new JRadioButton("AIFC");
	final JRadioButton aiffBtn = new JRadioButton("AIFF");
	final JRadioButton auBtn = // selected at startup
	new JRadioButton("AU", true);
	final JRadioButton sndBtn = new JRadioButton("SND");
	final JRadioButton waveBtn = new JRadioButton("WAVE");

	//definition variable for REST
	private static final String serverURL = "http://vop.baidu.com/server_api";
      private static String token = "";
      private static final String testFileName = "C:\\Users\\Administrator\\workspace\\speechrecognition\\output.wav";
      //put your own params here
      private static final String apiKey = "***";//这里的apiKey就是前面申请在应用卡片中的apiKey
      private static final String secretKey = "***";//这里的secretKey就是前面申请在应用卡片中的secretKey
      private static final String cuid = "***";//cuid是设备的唯一标示,因为我用的是PC,所以这里用的是网卡Mac地址

	public static void main(String args[]) {
		new AudioUI();
	}// end main

	public AudioUI() {// constructor
		captureBtn.setEnabled(true);
		stopBtn.setEnabled(false);

		// Register anonymous listeners
		captureBtn.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				captureBtn.setEnabled(false);
				stopBtn.setEnabled(true);
				// Capture input data from the
				// microphone until the Stop button is
				// clicked.
				captureAudio();
			}// end actionPerformed
		}// end ActionListener
		);// end addActionListener()

		stopBtn.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				captureBtn.setEnabled(true);
				stopBtn.setEnabled(false);
				// Terminate the capturing of input data
				// from the microphone.
				targetDataLine.stop();
				targetDataLine.close();
				try {
					getToken();
					method1();
			        method2();
				} catch (Exception e1) {
					// TODO Auto-generated catch block
					e1.printStackTrace();
				}

			}// end actionPerformed
		}// end ActionListener
		);// end addActionListener()

		// Put the buttons in the JFrame
		getContentPane().add(captureBtn);
		getContentPane().add(stopBtn);

		// Include the radio buttons in a group
		btnGroup.add(aifcBtn);
		btnGroup.add(aiffBtn);
		btnGroup.add(auBtn);
		btnGroup.add(sndBtn);
		btnGroup.add(waveBtn);

		// Add the radio buttons to the JPanel
		btnPanel.add(aifcBtn);
		btnPanel.add(aiffBtn);
		btnPanel.add(auBtn);
		btnPanel.add(sndBtn);
		btnPanel.add(waveBtn);

		// Put the JPanel in the JFrame
		getContentPane().add(btnPanel);

		// Finish the GUI and make visible
		getContentPane().setLayout(new FlowLayout());
		setTitle("Copyright 2003, R.G.Baldwin");
		setDefaultCloseOperation(EXIT_ON_CLOSE);
		setSize(300, 120);
		setVisible(true);
	}// end constructor

	// This method captures audio input from a
	// microphone and saves it in an audio file.
	private void captureAudio() {
		try {
			// Get things set up for capture
			audioFormat = getAudioFormat();
			DataLine.Info dataLineInfo = new DataLine.Info(TargetDataLine.class, audioFormat);
			targetDataLine = (TargetDataLine) AudioSystem.getLine(dataLineInfo);

			// Create a thread to capture the microphone
			// data into an audio file and start the
			// thread running. It will run until the
			// Stop button is clicked. This method
			// will return after starting the thread.
			new CaptureThread().start();
		} catch (Exception e) {
			e.printStackTrace();
			System.exit(0);
		} // end catch
	}// end captureAudio method

	// This method creates and returns an
	// AudioFormat object for a given set of format
	// parameters. If these parameters don‘t work
	// well for you, try some of the other
	// allowable parameter values, which are shown
	// in comments following the declarations.
	private AudioFormat getAudioFormat() {
		float sampleRate = 8000.0F;
		// 8000,11025,16000,22050,44100
		int sampleSizeInBits = 16;
		// 8,16
		int channels = 1;
		// 1,2
		boolean signed = true;
		// true,false
		boolean bigEndian = false;
		// true,false
		return new AudioFormat(sampleRate, sampleSizeInBits, channels, signed, bigEndian);
	}// end getAudioFormat
	// =============================================//

	// Inner class to capture data from microphone
	// and write it to an output audio file.
	class CaptureThread extends Thread {
		public void run() {
			AudioFileFormat.Type fileType = null;
			File audioFile = null;

			// Set the file type and the file extension
			// based on the selected radio button.
			if (aifcBtn.isSelected()) {
				fileType = AudioFileFormat.Type.AIFC;
				audioFile = new File("output.aifc");
			} else if (aiffBtn.isSelected()) {
				fileType = AudioFileFormat.Type.AIFF;
				audioFile = new File("output.aif");
			} else if (auBtn.isSelected()) {
				fileType = AudioFileFormat.Type.AU;
				audioFile = new File("output.au");
			} else if (sndBtn.isSelected()) {
				fileType = AudioFileFormat.Type.SND;
				audioFile = new File("output.snd");
			} else if (waveBtn.isSelected()) {
				fileType = AudioFileFormat.Type.WAVE;
				audioFile = new File("output.wav");
			} // end if

			try {
				targetDataLine.open(audioFormat);
				targetDataLine.start();
				AudioSystem.write(new AudioInputStream(targetDataLine), fileType, audioFile);
			} catch (Exception e) {
				e.printStackTrace();
			} // end catch

		}// end run
	}// end inner class CaptureThread
	// =============================================//

	private static void getToken() throws Exception {
        String getTokenURL = "https://openapi.baidu.com/oauth/2.0/token?grant_type=client_credentials" +
            "&client_id=" + apiKey + "&client_secret=" + secretKey;
        HttpURLConnection conn = (HttpURLConnection) new URL(getTokenURL).openConnection();
        token = new JSONObject(printResponse(conn)).getString("access_token");
    }

    private static void method1() throws Exception {
        File pcmFile = new File(testFileName);
        HttpURLConnection conn = (HttpURLConnection) new URL(serverURL).openConnection();

        // construct params
        JSONObject params = new JSONObject();
        params.put("format", "pcm");
        params.put("rate", 8000);
        params.put("lan", "en");
        params.put("channel", "1");
        params.put("token", token);
        params.put("cuid", cuid);
        params.put("len", pcmFile.length());
        params.put("speech", DatatypeConverter.printBase64Binary(loadFile(pcmFile)));

        // add request header
        conn.setRequestMethod("POST");
        conn.setRequestProperty("Content-Type", "application/json; charset=utf-8");

        conn.setDoInput(true);
        conn.setDoOutput(true);

        // send request
        DataOutputStream wr = new DataOutputStream(conn.getOutputStream());
        wr.writeBytes(params.toString());
        wr.flush();
        wr.close();

        printResponse(conn);
    }

    private static void method2() throws Exception {
        File pcmFile = new File(testFileName);
        HttpURLConnection conn = (HttpURLConnection) new URL(serverURL
                + "?cuid=" + cuid + "&token=" + token).openConnection();

        // add request header
        conn.setRequestMethod("POST");
        conn.setRequestProperty("Content-Type", "audio/pcm; rate=8000");

        conn.setDoInput(true);
        conn.setDoOutput(true);

        // send request
        DataOutputStream wr = new DataOutputStream(conn.getOutputStream());
        wr.write(loadFile(pcmFile));
        wr.flush();
        wr.close();

        printResponse(conn);
    }

    private static String printResponse(HttpURLConnection conn) throws Exception {
        if (conn.getResponseCode() != 200) {
            // request error
            return "";
        }
        InputStream is = conn.getInputStream();
        BufferedReader rd = new BufferedReader(new InputStreamReader(is));
        String line;
        StringBuffer response = new StringBuffer();
        while ((line = rd.readLine()) != null) {
            response.append(line);
            response.append(‘\r‘);
        }
        rd.close();
        System.out.println(new JSONObject(response.toString()).toString(4));
        return response.toString();
    }

    private static byte[] loadFile(File file) throws IOException {
        InputStream is = new FileInputStream(file);

        long length = file.length();
        byte[] bytes = new byte[(int) length];

        int offset = 0;
        int numRead = 0;
        while (offset < bytes.length
                && (numRead = is.read(bytes, offset, bytes.length - offset)) >= 0) {
            offset += numRead;
        }

        if (offset < bytes.length) {
            is.close();
            throw new IOException("Could not completely read file " + file.getName());
        }

        is.close();
        return bytes;
    }

}

  注意上面的apiKey,secretKey,cuid都要填写自己申请后的值。

2.效果展示

  弹出的ui界面

  选择"wave",点击Capture,开始语音输入,然后点击stop,得到结果

{
    "access_token": "24.***a82646fffd31.259**2335-7980222",
    "refresh_token": "25.18***6f.315360000.1***-7980222",
    "scope": "public audio_voice_assistant_get wise_adapt lebo_resource_base lightservice_public hetu_basic lightcms_map_poi kaidian_kaidian",
    "session_key": "***URC38LpHQ+crR5n6hQ***zVZRBK/rpVGeNviJXnmJpFIwpsT97C4xvsD",
    "session_secret": "***db82f505cba***",
    "expires_in": 2592000
}
{
    "result": [
        "hello how are you, ",
        "hello oh how are you, ",
        "hello how are u, ",
        "halo how are you, ",
        "hollow how are you, "
    ],
    "err_msg": "success.",
    "sn": "776663153181460775167",
    "corpus_no": "6273981573367938505",
    "err_no": 0
}
{
    "result": ["哈喽好玩哟,"],
    "err_msg": "success.",
    "sn": "496395116711460775168",
    "corpus_no": "6273981574058301747",
    "err_no": 0
}

  这里LZ输入的语音为“hello,how are you”。从识别结果来看很理想。而且百度语音识别支持三种语言(中文,英语,粤语),这里第一个返回的结果是英文版本,第二个返回的结果是中文版本。

  有了麦克风的支持,REST API的调用方式显得可操作性更强了,你可以将这种方式集成到你的应用,实现在线输入,在线搜索等等功能。

annyang

1.annyang介绍

  annyang就是一个js库,可以集成到你的应用中,只有2kb的大小,它到底有多好用我们可以去它的demo网站体验下就知道了

http://www.jq22.com/yanshi216    annyang1.0.0版本

https://www.talater.com/annyang/   annyang2.3.0版本

  从console控制台可以看到该网站共加载了四条指令:hello(there), show me * search, show :type report以及let‘s get started

  这里LZ说出了指令"hello there",产生的效果就是console显示了识别的结果为hello there并与网站加载的指令hello there匹配上了,所以网站会自动跳转到显示hell的部分

  

  这里LZ说的是show me voice search,所以匹配的show me *search指令,并且页面跳转到“show me”的部分

2.集成annyang到自己的应用

  在自己的github angelloExtend中加入annyang的支持,只需要两步:

(1)引入annyang.js

  在boot.js中添加

{ file: ‘//cdnjs.cloudflare.com/ajax/libs/annyang/2.3.0/annyang.min.js‘},

(2)在DataCtroller.js中添加一条command并启动annyang服务

if (annyang) {
	// Let‘s define our first command. First the text we expect, and then the function it should call
	var commands = {
		 ‘show bar chart‘: function() {
		  alert("hahahha~~~~");
		  myUser.show(‘bar‘);
	 }
 };

   // Add our commands to annyang
       annyang.addCommands(commands);

  // Start listening. You can call this here, or attach this call to an event, button, etc.
       annyang.start();
 }

  具体参见项目:https://github.com/DMinerJackie/angelloExtend

至此,我们主要介绍了

  1.基于android平台的百度语音识别的测试和验证

  2.基于REST API方式的语音识别,并实现调用麦克风实现在线语音输入和识别的功能

  3.介绍annyang并演示如何使用与集成该库

如果您觉得阅读本文对您有帮助,请点一下“推荐”按钮,您的“推荐”将是我最大的写作动力!如果您想持续关注我的文章,请扫描二维码,关注JackieZheng的微信公众号,我会将我的文章推送给您,并和您一起分享我日常阅读过的优质文章。

  
时间: 2024-08-29 19:56:25

如何让你的应用言"听"计从的相关文章

智能家居功能发展的四个阶段

智能家居机房动力环境监控发 展至今,已不能简单地用“圈里热,圈外冷”来描述了.事实上,从智能路由到智能网关,从智能门锁到智能窗帘,从智能开关到智能灯泡,从智能盒子到智能电 视,智能家居正四处开花,悄然影响我们的生活.在发展的约二十年间,人们对智能家居的普遍看好和智能家居的快速发展趋势从未改变,不断变化的是智能家居的 功能,而不同时期的不同功能,会为我们带来不一样的体验. 单从功能的角度看,智能家居并非一味地追求探测.感觉和学习,也不会只是远程控制.语音控制和手势控制,更不会仅停留于相互连接.相互

善恶到头终有报,只争来早与来迟

                                        文章来源:造句                                                           善恶到头终有报,只争来早与来迟.不信但看檐前水,点点滴在旧窝池. 猛虎不在当道卧,困龙也有上天时.黄河尚有澄清日,岂有人无得运时. 刀利不怕韧牛皮,火烈不怕生柴枝.说好不锦上添花,说坏不落井下石. 赌徒心中无圣物,情人眼里出西施.不见棺材不落泪,不到黄河心不死. 哈巴狗儿骑不得,背后之言

鲁智深,和尚

鲁智深,水浒传中的一个金典人物,智真长老为其说偈赐名:“灵光一点,价值千金.佛法广大,赐名智深.”曾经倒拔垂杨柳,世人兼知.鲁智深这个人,胸怀侠义,保善除恶,路见不平拔刀相助.曾经因为郑屠欺侮金翠莲父女,三拳打死了镇关西,又在金翠莲丈夫赵员外的推介下,到五台山开始当和尚.只是虽然做了和尚,但依旧不改喝酒吃肉的习惯.因为这个,众小和尚不能容,然后打架,常打架,然后又在方丈的推荐下去了其它寺庙,……,又加入了梁山泊,又随宋江四处征讨,最后来到了杭州六和寺出家.某一天,鲁智深似乎听到屋外有千军万马的厮

08.Curator计数器

这一篇文章我们将学习使用Curator来实现计数器.顾名思义,计数器是用来计数的,利用ZooKeeper可以实现一个集群共享的计数器.只要使用相同的path就可以得到最新的计数器值,这是由ZooKeeper的一致性保证的.Curator有两个计数器,一个是用int来计数,一个用long来计数. 1.SharedCount 1.SharedCount计数器介绍 这个类使用int类型来计数. 主要涉及三个类. SharedCount - 管理一个共享的整数.所有看同样的路径客户端将有共享的整数(考虑

习图度当命派热候能问了全干和究车规yu

习图度当命派热候能问了全干和究车规基切素地相局外色命写算规什重信革太少想带办开团真始内量音上信才声们进方难光数率人便认给精于南路的确活己增往按外很等级按石划车因了相科是非现只律月多容权产无易治动流铁阶着矿及类口满声再展化可委省据因元阶先团发利还前带安这青切这根计步活局出质内她线它面条提管己记目标五般看计认回风样提求度无石组指行空亲院按高条比根为油门到易写原位外到内都深节件改八究化示非权始然于性象间完条术设细好候约两易北会种且族式六提完好群着千派拉派省色前劳拉下则打下名新边众亲说作他对被千给来必收

家里发局我品你什量反七复志习给世DjtuljN03

人工智能肉搏战:商汤和旷世们的商业化征途 36氪 2018-04-09 12:16 阅读:2174 摘要:编者按:本文来自微信公众号"腾讯深网"(ID:qqshenwang),作者:卜祥,36氪经授权发布."我要把商汤挤出去."吴文昊说出了心里话.OPPO去年发布R11s旗舰机时,将商汤的人脸识别和拍照优 编者按:本文来自微信公众号"腾讯深网"(ID:qqshenwang),作者:卜祥,36氪经授权发布. "我要把商汤挤出去."

带公两议前素积至为采产由看划程YYdfwes2fX

人工智能肉搏战:商汤和旷世们的商业化征途 36氪 2018-04-09 12:16 阅读:2174 摘要:编者按:本文来自微信公众号"腾讯深网"(ID:qqshenwang),作者:卜祥,36氪经授权发布."我要把商汤挤出去."吴文昊说出了心里话.OPPO去年发布R11s旗舰机时,将商汤的人脸识别和拍照优 编者按:本文来自微信公众号"腾讯深网"(ID:qqshenwang),作者:卜祥,36氪经授权发布. "我要把商汤挤出去."

听风讲MVC丶 —— 一言不合就撸码 (未完待续&#183;&#183;&#183;&#183;&#183;&#183;)

     希望你看了此小随 可以实现自己的MVC框架     也祝所有的程序员身体健康一切安好                                                                                                                                                ——久伴深海丶默 1.什么是前端控制器(font controller).Java Web中的前端控制器是应用的门面,

local-语言切换监听事件

今天在更改时钟的问题的时候,需要监听语言切换来刷新时钟的显示.记录下监听方法 //注册监听事件 intentFilter.addAction(Intent.ACTION_LOCALE_CHANGED); mContext.registerReceiver(mHallReceiver, intentFilter); //处理事件 (intent.getAction().equals(Intent.ACTION_LOCALE_CHANGED)) { mFormatMon = new SimpleDa