| |
为应用程序加上语音能力有什么好处呢?粗略地讲,是为了趣味,它适合所有注重趣味的 应用,比如游戏。当然,从更严肃的角度来讲,它还涉及到应用的可用性问题。注意,这 里我考虑的不仅是可视化界面固有的不足,而且还有这样一些情形:一些时候,让双眼离 开当前的工作很不方便,甚至是不合法的。比如,假设有一个带语音功能的浏览器,你就 可以在外出散步或开车上班的同时,用听的方式浏览自己喜爱的网站。
从目前来看,邮件阅读器或许是语音技术更实际的应用,在javamail api的帮助下, 这一切已经可能。邮件阅读器可以定期地检查收件箱,然后用语音“you have new mail, would you like me to read it to you?”引起你的注意。按照类似的思路,我们还可以 考虑一个带语音功能的提醒器,把它连接到一个日历应用:它会及时地提醒你“don't forget your meeting with the boss in 10 minutes!”。
也许你已经被这些主意吸引,或者有了自己更好的主意,现在让我们继续。首先我将 介绍如何启用本文提供的语音引擎,这样,如果你认为语音引擎的实现细节过于复杂,就 可以直接使用它而忽略其实现细节。 一、试用语音引擎
要使用这个语音引擎,你必须在classpath中加入本文提供的javatalk.jar文件,然后从 命令行运行(或者从java程序调用)com.lotontech.speech.talker类。如果从命令行运 行,则命令为:
java com.lotontech.speech.talker "h|e|l|oo"
如果从java程序调用,则代码为:
com.lotontech.speech.talker talker=new com.lotontech.speech.talker();
talker.sayphoneword("h|e|l|oo");
现在,对于在命令行上(或者调用sayphoneword()方法时)提供的“h|e|l|oo”字符串,你 或许有所不解。下面我就来解释一下。
语音引擎的工作原理是把细小的声音样本连接起来,每一个样本都是人的语言发音(英 语)的一个最小单位。这些声音样本称为音素(allophone)。每一个因素对应一个、二 个或者三个字母。从前面“hello”的语音表示可以看出,一些字母组合的发音显而易见, 还有一些却不是很明显:
h -- 读音显而易见
e -- 读音显而易见
l -- 读音显而易见,但注意两个“l”被简缩成了一个“l”。
oo -- 应该读作“hello”中的读音,不应读作“bot”、“too”中的读音。
下面是一个有效音素的清单:
a : 如cat b : 如cab c : 如cat d : 如dot e : 如bet f : 如frog g : 如frog h : 如hog i : 如pig j : 如jig k : 如keg l : 如leg m : 如met n : 如begin o : 如not p : 如pot r : 如rot s : 如sat t : 如sat u : 如put v : 如have w : 如wet y : 如yet z : 如zoo aa : 如fake ay : 如hay ee : 如bee ii : 如high oo : 如go bb : b的变化形式,重音不同 dd : d的变化形式,重音不同 ggg : g的变化形式,重音不同 hh : h的变化形式,重音不同 ll : l的变化形式,重音不同 nn : n的变化形式,重音不同 rr : r的变化形式,重音不同 tt : t的变化形式,重音不同 yy : y的变化形式,重音不同 ar : 如car aer : 如care ch : 如which ck : 如check ear : 如beer er : 如later err : 如later (长音) ng : 如feeding or : 如law ou : 如zoo ouu : 如zoo (长音) ow : 如cow oy : 如boy sh : 如shut th : 如thing dth : 如this uh : u 的变化形式 wh : 如where zh : 如asian
人说话的时候,语音在整个句子之内起落变化。语调变化使得语音更自然、更富有感染 力,使得问句和陈述句能够相互区别。请考虑下面两个句子:
it is fake -- f|aa|k
is it fake? -- f|aa|k
也许你已经猜想到,提高语调的方法是使用大写字母。
以上就是使用该软件时你需要了解的东西。如果你对其后台实现细节感兴趣,请继续阅读。 二、实现语音引擎
语音引擎的实现只包括一个类,四个方法。它利用了j2se 1.3包含的java sound api。在 这里,我不准备全面地介绍这个api,但你可以通过实例学习它的用法。java sound api 并不是一个特别复杂的api,代码中的注释将告诉你必须了解的知识。
下面是talker类的基本定义:
package com.lotontech.speech;
import javax.sound.sampled.*;
import java.io.*;
import java.util.*;
import java.net.*;
public class talker
{
private sourcedataline line=null;
}
如果从命令行执行talker,下面的main()方法将作为入口点运行。main()方法获取第一个 命令行参数,然后把它传递给sayphoneword()方法:
/*
* 读出在命令行中指定的表示读音的字符串
*/
public static void main(string args[])
{
talker player=new talker();
if (args.length>0) player.sayphoneword(args[0]);
system.exit(0);
} sayphoneword()方法既可以通过上面的main()方法调用,也可以在java程序中直接调用。 从表面上看, sayphoneword()方法比较复杂,其实并非如此。实际上,它简单地遍历所 有单词的语音元素(在输入字符串中语音元素以“|”分隔),通过一个声音输出通道一个 元素一个元素地播放出来。为了让声音更自然一些,我把每一个声音样本的结尾和下一个 声音样本的开头合并了起来:
/*
* 读出指定的语音字符串
*/
public void sayphoneword(string word)
{
// 为上一个声音构造的模拟byte数组
byte[] previoussound=null;
// 把输入字符串分割成单独的音素
stringtokenizer st=new stringtokenizer(word,"|",false);
while (st.hasmoretokens())
{
// 为音素构造相应的文件名字
string thisphonefile=st.nexttoken();
thisphonefile="/allophones/"+thisphonefile+".au";
// 从声音文件读取数据
byte[] thissound=getsound(thisphonefile);
if (previoussound!=null)
{
// 如果可能的话,把前一个音素和当前音素合并
int mergecount=0;
if (previoussound.length>=500 && thissound.length>=500)
mergecount=500;
for (int i=0; i
{
previoussound[previoussound.length-mergecount+i]
=(byte)((previoussound[previoussound.length
-mergecount+i]+thissound[i])/2);
}
// 播放前一个音素
playsound(previoussound);
// 把经过截短的当前音素作为前一个音素
byte[] newsound=new byte[thissound.length-mergecount];
for (int ii=0; ii
newsound[ii]=thissound[ii+mergecount];
previoussound=newsound;
}
else
previoussound=thissound;
}
// 播放最后一个音素,清理声音通道
playsound(previoussound);
drain();
}
在sayphoneword()的后面,你可以看到它调用playsound()输出单个声音样本(即一个音 素),然后调用drain()清理声音通道。下面是playsound()的代码:
/*
* 该方法播放一个声音样本
*/
private void playsound(byte[] data)
{
if (data.length>0) line.write(data, 0, data.length);
}
下面是drain()的代码:
/*
* 该方法清理声音通道
*/
private void drain()
{
if (line!=null) line.drain();
try {thread.sleep(100);} catch (exception e) {}
} 现在回过头来看sayphoneword(),这里还有一个方法我们没有分析,即getsound()方法。
getsound()方法从一个au文件以字节数据的形式读入预先录制的声音样
|
|