分享
 
 
 

让Java说话!

王朝java/jsp·作者佚名  2006-01-08
窄屏简体版  字體: |||超大  

让Java说话!

为你的Java 1.3 应用程序和Applet添加说话能力

概要

这篇文章中,Tony Loton展示了不使用硬件和本地调用的,少于150行Java代码实现一个简单的语音引擎。此外,他提供了一个小zip文件,里面包含了使Java应用程序说话说需要的东西—仅仅用来娱乐或别的真正的应用程序。如果你刚刚接触Java Sound API,这篇文章将是一个很好的介绍。(1800字)

作者:Tony Loton

译者:Cocia Lin

为什么要使你的程序说话呢?首先,为了娱乐,这很适合象游戏这样的娱乐程序。并且还有很多严肃的应用领域。我想这虽然不是可视化界面的天生缺点,也是声音可用之处-- 或者过分一点 – 可以使你的眼睛离开你正在做的事情。

最近,我曾经应用一些技术在Web上获得HTML和XML信息的工作[请看 "Access the World's Biggest Database with Web DataBase Connectivity" (JavaWorld, March 2001)]。这让我将那个工作和我的这个想法结合来创建一个说话的Web浏览器。这样的一个浏览器可以使你听到你喜欢的网站上的信息摘录 – 新闻标题,例如 – 就象在外边溜狗或开车上班的途中收听收音机一样。当然,以现在的科技水平,你必须带上你的笔记本电脑和移动电话,但这些不切实际的设想在不久的将来,随着应用Java技术的智能电话的出现而变成现实,例如Nokia 9210(在美国叫9290).

也许对现在来说,能用的到的是一个email朗读器,这也得谢谢JavaMail API.这样的程序将定期的检查你的电子邮箱,并且你的注意被一个声音“你有新的email,你要我给你朗读吗?”吸引。相近的,考虑语音提醒 – 当连接到你的日常管理程序时 –- 电脑大喊“不要忘了10分钟后你和老板的会议!”

回到这些想法,或者你有更好的自己的想法,我们继续。我将演示怎样将我提供的zip文件添加的我们的工作中,这样,如果你觉得这些东西太难了,你就可以直接安装运行而跳过实现细节。

测试语音引擎

为了使用这个语音引擎,你需要将jw-0817-javatalk.zip添加到你的classpath,在命令行方式或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”的

这里列出了能用到的音体变(allophone):

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 (longer sound)

ng -- 例如 feeding

or -- 例如 law

ou -- 例如 zoo

ouu -- 例如 zoo (longer sound)

ow -- 例如 cow

oy -- 例如 boy

sh -- 例如 shut

th -- 例如 thing

dth -- 例如 this

uh -- 变调 u

wh -- 例如 where

zh -- 例如 Asian

人说话的每一个句子都有单词的升调和降调的变化。这个音调使说话听起来自然,富有感情,并且可以从句子语调确定这是疑问句。如果你听过Stephen Hawking的人造声音,你就能够理解我所说的了。考虑这两个句子:

It is fake -- f|aa|k

Is it fake? -- f|AA|k

你也许猜想,使用升调的方法是用大写字母。你要实际感受一下,我的提示是你要注意听元音字母

这是你使用这个软件需要知道的全部了,但是如果你对引擎罩下面的东西感兴趣,那么继续往下读。

实现语音引擎

语音引擎仅仅需要一个类来实现,包含四个方法。它使用J2SE1.3的Java Sound API。我不想提供一个全面的Java Sound 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;

}

如果你从命令行运行程序,下面的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程序或Applet直接调用。它看起来比它本身难理解。本质上,它简单的一步一步解释单词的语音变位allophone – 被”|”标志分割的输入文本 – 在把他们一个一个通过声音输出通道输出。为了让它听起来更自然,合并每一个声音的结尾到下一个声音的开头:

/*

*这个方法使输入的单词发音

*/

public void sayPhoneWord(String word)

{

// 为上一个声音设置一个字节数组

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<mergeCount;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.length; 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文件中读出声音的字节数据。当我说文件时,指的是我提供的zip文件里的资源。我强调这点差别,因为你得到JAR资源控制 – 使用getResource(..)方法 – 这不同于得到一个普通文件的控制权。

为了有一个语音一个语音的读出数据,转换到声音格式,实例化一个声音输出行(为什么他们叫它SourceDateLine,我不知道),组合这些字节数据,我在下面代码中提供给你说明:

/*

*这个方法从文件中读出单独的语音并且构造一个字节矢量

*/

private byte[] getSound(String fileName)

{

try

{

URL url=Talker.class.getResource(fileName);

AudioInputStream stream = AudioSystem.getAudioInputStream(url);

AudioFormat format = stream.getFormat();

转换一个ALAW/ULAW声音到PCM

if ((format.getEncoding() == AudioFormat.Encoding.ULAW) ||

(format.getEncoding() == AudioFormat.Encoding.ALAW))

{

AudioFormat tmpFormat = new AudioFormat(

AudioFormat.Encoding.PCM_SIGNED,

format.getSampleRate(),

format.getSampleSizeInBits() * 2,

format.getChannels(),

format.getFrameSize() * 2,

format.getFrameRate(),

true);

stream = AudioSystem.getAudioInputStream(tmpFormat, stream);

format = tmpFormat;

}

DataLine.Info info = new DataLine.Info(

Clip.class,

format,

((int) stream.getFrameLength() * format.getFrameSize()));

if (line==null)

{

// -- Output line not instantiated yet –

// -- Can we find a suitable kind of line? --

DataLine.Info outInfo = new DataLine.Info(SourceDataLine.class,

format);

if (!AudioSystem.isLineSupported(outInfo))

{

System.out.println("Line matching " + outInfo + " not supported.");

throw new Exception("Line matching " + outInfo + " not supported.");

}

打开资源数据行(输出行output line)

line = (SourceDataLine) AudioSystem.getLine(outInfo);

line.open(format, 50000);

line.start();

}

//一些尺寸计算

int frameSizeInBytes = format.getFrameSize();

int bufferLengthInFrames = line.getBufferSize() / 8;

int bufferLengthInBytes = bufferLengthInFrames * frameSizeInBytes;

byte[] data=new byte[bufferLengthInBytes];

//读出数据字节并计算

int numBytesRead = 0;

if ((numBytesRead = stream.read(data)) != -1)

{

int numBytesRemaining = numBytesRead;

}

//裁剪字节数组到正确尺寸

byte[] newData=new byte[numBytesRead];

for (int i=0; i<numBytesRead;i++)

newData[i]=data[i];

return newData;

}

catch (Exception e)

{

return new byte[0];

}

}

好了,就这么多。一个150行的语音合成器代码,包括说明。但这没有完全结束。

文本到语音(Text-to-speech)的转换

用语音学方法表示单词可能太乏味,所以,如果你想创建一个象我介绍一样的应用程序,你要提供原始文本。

研究过这个题目后,我在zip文件中提供一个实验性的文本到语音的转换类。当你运行它后,将输出给你你想要的语音表示。

在命令行模式,运行text-to-speech转换器:

java com.lotontech.speech.Converter "hello there"

你看到的输出类似下面这样:

hello -> h|e|l|oo

there -> dth|aer

或者,象这样运行它:

java com.lotontech.speech.Converter "I like to read JavaWorld"

看到(并且听到)这些:

i -> ii

like -> l|ii|k

to -> t|ouu

read -> r|ee|a|d

java -> j|a|v|a

world -> w|err|l|d

如果你想知道它是怎样工作的,我将告诉你我的方法很简单,应用通常的顺序的一套文本替换规则。有几个例子规则,你可能喜欢应用精神上的,顺序的方式,这些例子是”ant”,”want”,”wanted”,”unwanted”,”unique”:

替换 "*unique*"使用 "|y|ou|n|ee|k|"

替换"*want*"使用"|w|o|n|t|"

替换"*a*"使用"|a|"

替换"*e*"使用"|e|"

替换"*d*"使用"|d|"

替换"*n*" 使用"|n|"

替换"*u*"使用"|u|"

替换"*t*" 使用"|t|"

”unwanted”的顺序将是这样:

unwanted

un[|w|o|n|t|]ed (rule 2)

[|u|][|n|][|w|o|n|t|][|e|][|d|] (rules 4, 5, 6, 7)

u|n|w|o|n|t|e|d (with surplus characters removed)

你应该看到,包含ant的want被用几种不同的方式朗读。你也应该看到对于unique来说的特殊情况,应该被读为y|ou..而不是u|n….

电脑里的精灵,对你说话

这篇文章提供一个可以使用Java 1.3运行的简便的语音引擎。如果你研究这些代码,你可以得到一些关于JavaSound API播放音频片断的有用方法。要想使这个引擎真的能用,你要思考文本到语音的转换方法,这真的是我的一个主要想法。在这个引擎中,你要想出大量的文本转换规则,还要应用一些好的优先顺序。我希望你有比我强的毅力。

最后,你可能还记得我说过的Nokia 9210。我有一部,它支持Java,我决定用Java使它说话。我也想使applet(Java2的以前版本)在浏览器中说话。这些技术依靠J2SE 1.3声音引擎,现在是可用的。一个不同的方法是需要的,依靠简单的Java AudioClip 接口。不像你想象的那样简单,但我在其上工作。

关于作者

Tony Loton为他的公司工作 – LOTONTech Limited – 提供软件解决方案,顾问,培训和技术写作服务。写作的小虫好像在这一年里始终叮咬着他,他为John Wiley & Sons 和 Wrox Press出版社写书。

关于译者

Cocia Lin(cocia@163.com)是程序员。它拥有学士学位,现在专攻Java相关技术,刚刚开始在计算机领域折腾。

相关资源

You'll find the speech engine and related source code in the jw-0817-javatalk.zip file:

http://www.javaworld.com/jw-08-2001/javatalk/jw-0817-javatalk.zip

Go to java.sun.com's "Java Sound API" page for documentation, download information, and FAQ:

http://java.sun.com/products/java-media/sound/

To see how the speech engine will combine with Webpages to build my talking browser, read "Access the World's Biggest Database with Web Database Connectivity," Tony Loton (JavaWorld, March 2001):

http://www.javaworld.com/javaworld/jw-03-2001/jw-0316-webdb.html

In "Make an EJB from Any Java Class with Java Reflection" (JavaWorld, December 2000), Tony Loton explains how to turn any Java class into an EJB, and any single-tier Java application into an enterprise application:

http://www.javaworld.com/jw-12-2000/jw-1215-anyclass.html

"Program Multimedia with JMF," Budi Kurniawan (JavaWorld):

Part 1: Go multimedia by learning how the Java Media Framework compares to your stereo system (April 2001)

Part 2: Jump into Java Media Framework's important classes and interfaces (May 2001)

"Add MP3 capabilities to Java Sound with SPI," Dan Becker (JavaWorld, November 2000):

http://www.javaworld.com/jw-11-2000/jw-1103-mp3.html

Sign up for the JavaWorld This Week free weekly email newsletter to learn what's new at JavaWorld:

http://www.idg.net/jw-subscribe

You'll find a wealth of IT-related articles from our sister publications at IDG.net

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有